Bug 113663 [plan item] Refactor the runtime. Initail commit
diff --git a/bundles/org.eclipse.equinox.common/.classpath b/bundles/org.eclipse.equinox.common/.classpath
new file mode 100644
index 0000000..751c8f2
--- /dev/null
+++ b/bundles/org.eclipse.equinox.common/.classpath
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="src" path="src"/>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+ <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/bundles/org.eclipse.equinox.common/.cvsignore b/bundles/org.eclipse.equinox.common/.cvsignore
new file mode 100644
index 0000000..ba077a4
--- /dev/null
+++ b/bundles/org.eclipse.equinox.common/.cvsignore
@@ -0,0 +1 @@
+bin
diff --git a/bundles/org.eclipse.equinox.common/.project b/bundles/org.eclipse.equinox.common/.project
new file mode 100644
index 0000000..2289dee
--- /dev/null
+++ b/bundles/org.eclipse.equinox.common/.project
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>org.eclipse.equinox.common</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.pde.ManifestBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.pde.SchemaBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.pde.PluginNature</nature>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ </natures>
+</projectDescription>
diff --git a/bundles/org.eclipse.equinox.common/META-INF/MANIFEST.MF b/bundles/org.eclipse.equinox.common/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..d7bfc74
--- /dev/null
+++ b/bundles/org.eclipse.equinox.common/META-INF/MANIFEST.MF
@@ -0,0 +1,12 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: %pluginName
+Bundle-SymbolicName: org.eclipse.equinox.common; singleton:=true
+Bundle-Version: 1.0.0.qualifier
+Bundle-Localization: plugin
+Export-Package: org.eclipse.core.internal.runtime;x-friends:="org.eclipse.core.contenttype,org.eclipse.core.jobs,org.eclipse.equinox.preferences,org.eclipse.equinox.registry,org.eclipse.core.runtime,org.eclipse.core.runtime.compatibility",
+ org.eclipse.core.runtime
+Bundle-Vendor: %providerName
+Bundle-Activator: org.eclipse.core.internal.runtime.Activator
+Require-Bundle: system.bundle;resolution:=optional,
+ org.eclipse.equinox.supplement;resolution:=optional
diff --git a/bundles/org.eclipse.equinox.common/about.html b/bundles/org.eclipse.equinox.common/about.html
new file mode 100644
index 0000000..cdc1e50
--- /dev/null
+++ b/bundles/org.eclipse.equinox.common/about.html
@@ -0,0 +1,24 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN">
+<html>
+<head>
+<title>About</title>
+<meta http-equiv=Content-Type content="text/html; charset=ISO-8859-1">
+</head>
+<body lang="EN-US">
+<h2>About This Content</h2>
+
+<p>September 26, 2005</p>
+<h3>License</h3>
+
+<p>The Eclipse Foundation makes available all content in this plug-in ("Content"). Unless otherwise indicated below, the Content is provided to you under the terms and conditions of the
+Eclipse Public License Version 1.0 ("EPL"). A copy of the EPL is available at <a href="http://www.eclipse.org/legal/epl-v10.html" target="_blank">http://www.eclipse.org/legal/epl-v10.html</a>.
+For purposes of the EPL, "Program" will mean the Content.</p>
+
+<p>If you did not receive this Content directly from the Eclipse Foundation, the Content is being redistributed by another party ("Redistributor") and different terms and conditions may
+apply to your use of any object code in the Content. Check the Redistributor's license that was provided with the Content. If no such license exists, contact the Redistributor. Unless otherwise
+indicated below, the terms and conditions of the EPL still apply to any source code in the Content.</p>
+
+<small>Java and all Java-based trademarks and logos are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries.</small>
+
+</body>
+</html>
diff --git a/bundles/org.eclipse.equinox.common/build.properties b/bundles/org.eclipse.equinox.common/build.properties
new file mode 100644
index 0000000..c1e8ad7
--- /dev/null
+++ b/bundles/org.eclipse.equinox.common/build.properties
@@ -0,0 +1,17 @@
+###############################################################################
+# Copyright (c) 2005 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
+###############################################################################
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+ .,\
+ plugin.properties,\
+ about.html
+src.includes = about.html
diff --git a/bundles/org.eclipse.equinox.common/plugin.properties b/bundles/org.eclipse.equinox.common/plugin.properties
new file mode 100644
index 0000000..9badd2f
--- /dev/null
+++ b/bundles/org.eclipse.equinox.common/plugin.properties
@@ -0,0 +1,12 @@
+###############################################################################
+# Copyright (c) 2005 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
+###############################################################################
+pluginName = Common Eclipse Runtime
+providerName = Eclipse.org
diff --git a/bundles/org.eclipse.equinox.common/src/org/eclipse/core/internal/runtime/Activator.java b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/internal/runtime/Activator.java
new file mode 100644
index 0000000..324f169
--- /dev/null
+++ b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/internal/runtime/Activator.java
@@ -0,0 +1,46 @@
+/*******************************************************************************
+ * Copyright (c) 2005 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.runtime;
+
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+
+/**
+ * The Common runtime plugin class.
+ *
+ * This class can only be used if OSGi plugin is available.
+ */
+public class Activator implements BundleActivator {
+
+ /**
+ * The bundle context associated this plug-in
+ */
+ private static BundleContext bundleContext;
+
+ /**
+ * This method is called upon plug-in activation
+ */
+ public void start(BundleContext context) throws Exception {
+ bundleContext = context;
+ }
+
+ /**
+ * This method is called when the plug-in is stopped
+ */
+ public void stop(BundleContext context) throws Exception {
+ CommonOSGiUtils.getDefault().closeServices();
+ bundleContext = null;
+ }
+
+ static BundleContext getContext() {
+ return bundleContext;
+ }
+}
diff --git a/bundles/org.eclipse.equinox.common/src/org/eclipse/core/internal/runtime/AdapterManager.java b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/internal/runtime/AdapterManager.java
new file mode 100644
index 0000000..55e9a02
--- /dev/null
+++ b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/internal/runtime/AdapterManager.java
@@ -0,0 +1,353 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2005 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.runtime;
+
+import java.util.*;
+import org.eclipse.core.runtime.IAdapterFactory;
+import org.eclipse.core.runtime.IAdapterManager;
+
+/**
+ * This class is the standard implementation of <code>IAdapterManager</code>. It provides
+ * fast lookup of property values with the following semantics:
+ * <ul>
+ * <li>At most one factory will be invoked per property lookup
+ * <li>If multiple installed factories provide the same adapter, only the first found in
+ * the search order will be invoked.
+ * <li>The search order from a class with the definition <br>
+ * <code>class X extends Y implements A, B</code><br> is as follows: <il>
+ * <li>the target's class: X
+ * <li>X's superclasses in order to <code>Object</code>
+ * <li>a breadth-first traversal of the target class's interfaces in the order returned by
+ * <code>getInterfaces</code> (in the example, A and its superinterfaces then B and its
+ * superinterfaces) </il>
+ * </ul>
+ *
+ * @see IAdapterFactory
+ * @see IAdapterManager
+ */
+public final class AdapterManager implements IAdapterManager {
+ /**
+ * Cache of adapters for a given adaptable class. Maps String -> Map
+ * (adaptable class name -> (adapter class name -> factory instance))
+ */
+ protected HashMap adapterLookup;
+ /**
+ * Cache of classes for a given type name. Avoids too many loadClass calls.
+ * (factory -> (type name -> clazz))
+ */
+ protected HashMap classLookup;
+ /**
+ * Cache of class lookup order. This avoids having to compute often, and
+ * provides clients with quick lookup for instanceOf checks based on type name.
+ */
+ protected HashMap classSearchOrderLookup;
+ /**
+ * Map of factories, keyed by <code>String</code>, fully qualified class name of
+ * the adaptable class that the factory provides adapters for. Value is a <code>List</code>
+ * of <code>IAdapterFactory</code>.
+ */
+ protected final HashMap factories;
+
+ private static final AdapterManager singleton = new AdapterManager();
+
+ public static AdapterManager getDefault() {
+ return singleton;
+ }
+
+ /**
+ * Private constructor to block instance creation.
+ */
+ private AdapterManager() {
+ factories = new HashMap(5);
+ adapterLookup = null;
+ }
+
+ /**
+ * Given a type name, add all of the factories that respond to those types into
+ * the given table. Each entry will be keyed by the adapter class name (supplied in
+ * IAdapterFactory.getAdapterList).
+ */
+ private void addFactoriesFor(String typeName, Map table) {
+ List factoryList = (List) factories.get(typeName);
+ if (factoryList == null)
+ return;
+ for (int i = 0, imax = factoryList.size(); i < imax; i++) {
+ IAdapterFactory factory = (IAdapterFactory) factoryList.get(i);
+ if (factory instanceof IAdapterFactoryExt) {
+ String[] adapters = ((IAdapterFactoryExt) factory).getAdapterNames();
+ for (int j = 0; j < adapters.length; j++) {
+ if (table.get(adapters[j]) == null)
+ table.put(adapters[j], factory);
+ }
+ } else {
+ Class[] adapters = factory.getAdapterList();
+ for (int j = 0; j < adapters.length; j++) {
+ String adapterName = adapters[j].getName();
+ if (table.get(adapterName) == null)
+ table.put(adapterName, factory);
+ }
+ }
+ }
+ }
+
+ private void cacheClassLookup(IAdapterFactory factory, Class clazz) {
+ //cache reference to lookup to protect against concurrent flush
+ HashMap lookup = classLookup;
+ if (lookup == null)
+ classLookup = lookup = new HashMap(4);
+ HashMap classes = (HashMap) lookup.get(factory);
+ if (classes == null) {
+ classes = new HashMap(4);
+ lookup.put(factory, classes);
+ }
+ classes.put(clazz.getName(), clazz);
+ }
+
+ private Class cachedClassForName(IAdapterFactory factory, String typeName) {
+ Class clazz = null;
+ //cache reference to lookup to protect against concurrent flush
+ HashMap lookup = classLookup;
+ if (lookup != null) {
+ HashMap classes = (HashMap) lookup.get(factory);
+ if (classes != null) {
+ clazz = (Class) classes.get(typeName);
+ }
+ }
+ return clazz;
+ }
+
+ /**
+ * Returns the class with the given fully qualified name, or null
+ * if that class does not exist or belongs to a plug-in that has not
+ * yet been loaded.
+ */
+ private Class classForName(IAdapterFactory factory, String typeName) {
+ Class clazz = cachedClassForName(factory, typeName);
+ if (clazz == null) {
+ try {
+ if (factory instanceof IAdapterFactoryExt)
+ factory = ((IAdapterFactoryExt) factory).loadFactory(false);
+ if (factory != null) {
+ clazz = factory.getClass().getClassLoader().loadClass(typeName);
+ cacheClassLookup(factory, clazz);
+ }
+ } catch (ClassNotFoundException e) {
+ // class not yet loaded
+ }
+ }
+ return clazz;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.core.runtime.IAdapterManager#getAdapterTypes(java.lang.Class)
+ */
+ public String[] computeAdapterTypes(Class adaptable) {
+ Set types = getFactories(adaptable).keySet();
+ return (String[]) types.toArray(new String[types.size()]);
+ }
+
+ /**
+ * Computes the adapters that the provided class can adapt to, along
+ * with the factory object that can perform that transformation. Returns
+ * a table of adapter class name to factory object.
+ * @param adaptable
+ */
+ private Map getFactories(Class adaptable) {
+ //cache reference to lookup to protect against concurrent flush
+ HashMap lookup = adapterLookup;
+ if (lookup == null)
+ adapterLookup = lookup = new HashMap(30);
+ Map table = (Map) lookup.get(adaptable.getName());
+ if (table == null) {
+ // calculate adapters for the class
+ table = new HashMap(4);
+ Class[] classes = computeClassOrder(adaptable);
+ for (int i = 0; i < classes.length; i++)
+ addFactoriesFor(classes[i].getName(), table);
+ // cache the table
+ lookup.put(adaptable.getName(), table);
+ }
+ return table;
+ }
+
+ public Class[] computeClassOrder(Class adaptable) {
+ List classes = null;
+ //cache reference to lookup to protect against concurrent flush
+ HashMap lookup = classSearchOrderLookup;
+ if (lookup != null)
+ classes = (List) lookup.get(adaptable);
+ // compute class order only if it hasn't been cached before
+ if (classes == null) {
+ classes = new ArrayList();
+ computeClassOrder(adaptable, classes);
+ if (lookup == null)
+ classSearchOrderLookup = lookup = new HashMap();
+ lookup.put(adaptable, classes);
+ }
+ return (Class[]) classes.toArray(new Class[classes.size()]);
+ }
+
+ /**
+ * Builds and returns a table of adapters for the given adaptable type.
+ * The table is keyed by adapter class name. The
+ * value is the <b>sole<b> factory that defines that adapter. Note that
+ * if multiple adapters technically define the same property, only the
+ * first found in the search order is considered.
+ *
+ * Note that it is important to maintain a consistent class and interface
+ * lookup order. See the class comment for more details.
+ */
+ private void computeClassOrder(Class adaptable, Collection classes) {
+ Class clazz = adaptable;
+ Set seen = new HashSet(4);
+ while (clazz != null) {
+ classes.add(clazz);
+ computeInterfaceOrder(clazz.getInterfaces(), classes, seen);
+ clazz = clazz.getSuperclass();
+ }
+ }
+
+ private void computeInterfaceOrder(Class[] interfaces, Collection classes, Set seen) {
+ List newInterfaces = new ArrayList(interfaces.length);
+ for (int i = 0; i < interfaces.length; i++) {
+ Class interfac = interfaces[i];
+ if (seen.add(interfac)) {
+ //note we cannot recurse here without changing the resulting interface order
+ classes.add(interfac);
+ newInterfaces.add(interfac);
+ }
+ }
+ for (Iterator it = newInterfaces.iterator(); it.hasNext();)
+ computeInterfaceOrder(((Class) it.next()).getInterfaces(), classes, seen);
+ }
+
+ /**
+ * Flushes the cache of adapter search paths. This is generally required whenever an
+ * adapter is added or removed.
+ * <p>
+ * It is likely easier to just toss the whole cache rather than trying to be smart
+ * and remove only those entries affected.
+ * </p>
+ */
+ public synchronized void flushLookup() {
+ adapterLookup = null;
+ classLookup = null;
+ classSearchOrderLookup = null;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.core.runtime.IAdapterManager#getAdapter(java.lang.Object, java.lang.Class)
+ */
+ public Object getAdapter(Object adaptable, Class adapterType) {
+ IAdapterFactory factory = (IAdapterFactory) getFactories(adaptable.getClass()).get(adapterType.getName());
+ Object result = null;
+ if (factory != null)
+ result = factory.getAdapter(adaptable, adapterType);
+ if (result == null && adapterType.isInstance(adaptable))
+ return adaptable;
+ return result;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.core.runtime.IAdapterManager#getAdapter(java.lang.Object, java.lang.Class)
+ */
+ public Object getAdapter(Object adaptable, String adapterType) {
+ return getAdapter(adaptable, adapterType, false);
+ }
+
+ /**
+ * Returns an adapter of the given type for the provided adapter.
+ * @param adaptable the object to adapt
+ * @param adapterType the type to adapt the object to
+ * @param force <code>true</code> if the plug-in providing the
+ * factory should be activated if necessary. <code>false</code>
+ * if no plugin activations are desired.
+ */
+ private Object getAdapter(Object adaptable, String adapterType, boolean force) {
+ IAdapterFactory factory = (IAdapterFactory) getFactories(adaptable.getClass()).get(adapterType);
+ if (force && factory instanceof IAdapterFactoryExt)
+ factory = ((IAdapterFactoryExt) factory).loadFactory(true);
+ Object result = null;
+ if (factory != null) {
+ Class clazz = classForName(factory, adapterType);
+ if (clazz != null)
+ result = factory.getAdapter(adaptable, clazz);
+ }
+ if (result == null && adaptable.getClass().getName().equals(adapterType))
+ return adaptable;
+ return result;
+ }
+
+ public boolean hasAdapter(Object adaptable, String adapterTypeName) {
+ return getFactories(adaptable.getClass()).get(adapterTypeName) != null;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.core.runtime.IAdapterManager#loadAdapter(java.lang.Object, java.lang.String)
+ */
+ public Object loadAdapter(Object adaptable, String adapterTypeName) {
+ return getAdapter(adaptable, adapterTypeName, true);
+ }
+
+ /*
+ * @see IAdapterManager#registerAdapters
+ */
+ public synchronized void registerAdapters(IAdapterFactory factory, Class adaptable) {
+ registerFactory(factory, adaptable.getName());
+ flushLookup();
+ }
+
+ /*
+ * @see IAdapterManager#registerAdapters
+ */
+ public void registerFactory(IAdapterFactory factory, String adaptableType) {
+ List list = (List) factories.get(adaptableType);
+ if (list == null) {
+ list = new ArrayList(5);
+ factories.put(adaptableType, list);
+ }
+ list.add(factory);
+ }
+
+ /*
+ * @see IAdapterManager#unregisterAdapters
+ */
+ public synchronized void unregisterAdapters(IAdapterFactory factory) {
+ for (Iterator it = factories.values().iterator(); it.hasNext();)
+ ((List) it.next()).remove(factory);
+ flushLookup();
+ }
+
+ /*
+ * @see IAdapterManager#unregisterAdapters
+ */
+ public synchronized void unregisterAdapters(IAdapterFactory factory, Class adaptable) {
+ List factoryList = (List) factories.get(adaptable.getName());
+ if (factoryList == null)
+ return;
+ factoryList.remove(factory);
+ flushLookup();
+ }
+
+ /*
+ * Shuts down the adapter manager by removing all factories
+ * and removing the registry change listener. Should only be
+ * invoked during platform shutdown.
+ */
+ public synchronized void unregisterAllAdapters() {
+ factories.clear();
+ flushLookup();
+ }
+
+ public HashMap getFactories() {
+ return factories;
+ }
+}
diff --git a/bundles/org.eclipse.equinox.common/src/org/eclipse/core/internal/runtime/CommonMessages.java b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/internal/runtime/CommonMessages.java
new file mode 100644
index 0000000..af00d3d
--- /dev/null
+++ b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/internal/runtime/CommonMessages.java
@@ -0,0 +1,58 @@
+/**********************************************************************
+ * Copyright (c) 2005 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 - Initial API and implementation
+ **********************************************************************/
+package org.eclipse.core.internal.runtime;
+
+import org.eclipse.osgi.util.NLS;
+
+// Common runtime plugin message catalog
+public class CommonMessages extends NLS {
+ private static final String BUNDLE_NAME = "org.eclipse.core.internal.runtime.commonMessages"; //$NON-NLS-1$
+
+ public static String ok;
+
+ // metadata
+ public static String meta_couldNotCreate;
+ public static String meta_instanceDataUnspecified;
+ public static String meta_noDataModeSpecified;
+ public static String meta_notDir;
+ public static String meta_readonly;
+ public static String meta_pluginProblems;
+
+ // parsing/resolve
+ public static String parse_doubleSeparatorVersion;
+ public static String parse_emptyPluginVersion;
+ public static String parse_fourElementPluginVersion;
+ public static String parse_numericMajorComponent;
+ public static String parse_numericMinorComponent;
+ public static String parse_numericServiceComponent;
+ public static String parse_oneElementPluginVersion;
+
+ public static String parse_postiveMajor;
+ public static String parse_postiveMinor;
+ public static String parse_postiveService;
+ public static String parse_separatorEndVersion;
+ public static String parse_separatorStartVersion;
+
+ // FileMananger messages
+ public static String fileManager_cannotLock;
+ public static String fileManager_couldNotSave;
+ public static String fileManager_updateFailed;
+ public static String fileManager_illegalInReadOnlyMode;
+ public static String fileManager_notOpen;
+
+ static {
+ // load message values from bundle file
+ reloadMessages();
+ }
+
+ public static void reloadMessages() {
+ NLS.initializeMessages(BUNDLE_NAME, CommonMessages.class);
+ }
+}
\ No newline at end of file
diff --git a/bundles/org.eclipse.equinox.common/src/org/eclipse/core/internal/runtime/CommonOSGiUtils.java b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/internal/runtime/CommonOSGiUtils.java
new file mode 100644
index 0000000..b4aae6b
--- /dev/null
+++ b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/internal/runtime/CommonOSGiUtils.java
@@ -0,0 +1,180 @@
+/*******************************************************************************
+ * Copyright (c) 2005 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.runtime;
+
+import java.util.Date;
+import java.util.ResourceBundle;
+import org.eclipse.osgi.framework.log.FrameworkLog;
+import org.eclipse.osgi.service.datalocation.Location;
+import org.eclipse.osgi.service.localization.BundleLocalization;
+import org.osgi.framework.*;
+import org.osgi.service.packageadmin.PackageAdmin;
+import org.osgi.util.tracker.ServiceTracker;
+
+/**
+ * The class contains a set of helper methods for the runtime content plugin.
+ * The following utility methods are supplied:
+ * - provides framework log
+ * - provides some bundle discovery funtionality
+ * - provides some location services
+ *
+ * The closeServices() method should be called before the plugin is stopped.
+ *
+ * This class can only be used if OSGi plugin is available.
+ *
+ * @since org.eclipse.equinox.common 1.0
+ */
+public class CommonOSGiUtils {
+ private ServiceTracker logTracker = null;
+ private ServiceTracker bundleTracker = null;
+ private ServiceTracker instanceLocationTracker = null;
+ private ServiceTracker localizationTracker = null;
+
+ // OSGI system properties. Copied from EclipseStarter
+ public static final String PROP_INSTANCE_AREA = "osgi.instance.area"; //$NON-NLS-1$
+
+ private static final CommonOSGiUtils singleton = new CommonOSGiUtils();
+
+ public static CommonOSGiUtils getDefault() {
+ return singleton;
+ }
+
+ /**
+ * Private constructor to block instance creation.
+ */
+ private CommonOSGiUtils() {
+ super();
+ initServices();
+ }
+
+ /**
+ * Print a debug message to the console.
+ * Pre-pend the message with the current date and the name of the current thread.
+ */
+ public static void message(String message) {
+ StringBuffer buffer = new StringBuffer();
+ buffer.append(new Date(System.currentTimeMillis()));
+ buffer.append(" - ["); //$NON-NLS-1$
+ buffer.append(Thread.currentThread().getName());
+ buffer.append("] "); //$NON-NLS-1$
+ buffer.append(message);
+ System.out.println(buffer.toString());
+ }
+
+ private void initServices() {
+ BundleContext context = Activator.getContext();
+ if (context == null) {
+ message("CommonOSGiUtils called before plugin started"); //$NON-NLS-1$
+ return;
+ }
+
+ logTracker = new ServiceTracker(context, FrameworkLog.class.getName(), null);
+ logTracker.open();
+
+ bundleTracker = new ServiceTracker(context, PackageAdmin.class.getName(), null);
+ bundleTracker.open();
+
+ // locations
+
+ final String FILTER_PREFIX = "(&(objectClass=org.eclipse.osgi.service.datalocation.Location)(type="; //$NON-NLS-1$
+ Filter filter = null;
+ try {
+ filter = context.createFilter(FILTER_PREFIX + PROP_INSTANCE_AREA + "))"); //$NON-NLS-1$
+ } catch (InvalidSyntaxException e) {
+ // ignore this. It should never happen as we have tested the above format.
+ }
+ instanceLocationTracker = new ServiceTracker(context, filter, null);
+ instanceLocationTracker.open();
+ }
+
+ void closeServices() {
+ if (localizationTracker != null) {
+ localizationTracker.close();
+ localizationTracker = null;
+ }
+ if (logTracker != null) {
+ logTracker.close();
+ logTracker = null;
+ }
+ if (bundleTracker != null) {
+ bundleTracker.close();
+ bundleTracker = null;
+ }
+ if (instanceLocationTracker != null) {
+ instanceLocationTracker.close();
+ instanceLocationTracker = null;
+ }
+ }
+
+ public FrameworkLog getFrameworkLog() {
+ if (logTracker != null)
+ return (FrameworkLog) logTracker.getService();
+ message("Log tracker is not set"); //$NON-NLS-1$
+ return null;
+ }
+
+ public Bundle[] getFragments(Bundle bundle) {
+ if (bundleTracker == null) {
+ message("Bundle tracker is not set"); //$NON-NLS-1$
+ return null;
+ }
+ PackageAdmin packageAdmin = (PackageAdmin) bundleTracker.getService();
+ if (packageAdmin == null)
+ return null;
+ return packageAdmin.getFragments(bundle);
+ }
+
+ public Location getInstanceLocation() {
+ if (instanceLocationTracker != null)
+ return (Location) instanceLocationTracker.getService();
+ else
+ return null;
+ }
+
+ public ResourceBundle getLocalization(Bundle bundle, String locale) {
+ if (localizationTracker == null) {
+ BundleContext context = Activator.getContext();
+ if (context == null) {
+ message("ResourceTranslator called before plugin is started"); //$NON-NLS-1$
+ return null;
+ }
+ localizationTracker = new ServiceTracker(context, BundleLocalization.class.getName(), null);
+ localizationTracker.open();
+ }
+ BundleLocalization location = (BundleLocalization) localizationTracker.getService();
+ if (location != null)
+ return location.getLocalization(bundle, locale);
+
+ return null;
+ }
+
+ /**
+ * Returns the bundle id of the bundle that contains the provided object, or
+ * <code>null</code> if the bundle could not be determined.
+ */
+ public String getBundleId(Object object) {
+ if (object == null)
+ return null;
+ if (bundleTracker == null) {
+ message("Bundle tracker is not set"); //$NON-NLS-1$
+ return null;
+ }
+ PackageAdmin packageAdmin = (PackageAdmin) bundleTracker.getService();
+ if (packageAdmin == null)
+ return null;
+
+ Bundle source = packageAdmin.getBundle(object.getClass());
+ if (source != null && source.getSymbolicName() != null)
+ return source.getSymbolicName();
+ return null;
+ }
+
+}
diff --git a/bundles/org.eclipse.equinox.common/src/org/eclipse/core/internal/runtime/DataArea.java b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/internal/runtime/DataArea.java
new file mode 100644
index 0000000..6eaf5f6
--- /dev/null
+++ b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/internal/runtime/DataArea.java
@@ -0,0 +1,126 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2005 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.runtime;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+import org.eclipse.core.runtime.*;
+import org.eclipse.osgi.service.datalocation.Location;
+import org.eclipse.osgi.util.NLS;
+import org.osgi.framework.Bundle;
+
+/**
+ * This class can only be used if OSGi plugin is available
+ */
+public class DataArea {
+ /* package */static final String F_META_AREA = ".metadata"; //$NON-NLS-1$
+ /* package */static final String F_PLUGIN_DATA = ".plugins"; //$NON-NLS-1$
+ /* package */static final String F_LOG = ".log"; //$NON-NLS-1$
+ /**
+ * Internal name of the preference storage file (value <code>"pref_store.ini"</code>) in this plug-in's (read-write) state area.
+ */
+ /* package */static final String PREFERENCES_FILE_NAME = "pref_store.ini"; //$NON-NLS-1$
+
+ private IPath location; //The location of the instance data
+ private boolean initialized = false;
+
+ protected void assertLocationInitialized() throws IllegalStateException {
+ if (location != null && initialized)
+ return;
+ Location service = CommonOSGiUtils.getDefault().getInstanceLocation();
+ if (service == null)
+ throw new IllegalStateException(CommonMessages.meta_noDataModeSpecified);
+ try {
+ URL url = service.getURL();
+ if (url == null)
+ throw new IllegalStateException(CommonMessages.meta_instanceDataUnspecified);
+ // TODO assume the URL is a file:
+ // Use the new File technique to ensure that the resultant string is
+ // in the right format (e.g., leading / removed from /c:/foo etc)
+ location = new Path(new File(url.getFile()).toString());
+ initializeLocation();
+ } catch (CoreException e) {
+ throw new IllegalStateException(e.getMessage());
+ }
+ }
+
+ public IPath getMetadataLocation() throws IllegalStateException {
+ assertLocationInitialized();
+ return location.append(F_META_AREA);
+ }
+
+ public IPath getInstanceDataLocation() throws IllegalStateException {
+ assertLocationInitialized();
+ return location;
+ }
+
+ public IPath getLogLocation() throws IllegalStateException {
+ return new Path(CommonOSGiUtils.getDefault().getFrameworkLog().getFile().getAbsolutePath());
+ }
+
+ /**
+ * Returns the read/write location in which the given bundle can manage private state.
+ */
+ public IPath getStateLocation(Bundle bundle) throws IllegalStateException {
+ assertLocationInitialized();
+ return getStateLocation(bundle.getSymbolicName());
+ }
+
+ public IPath getStateLocation(String bundleName) throws IllegalStateException {
+ assertLocationInitialized();
+ return getMetadataLocation().append(F_PLUGIN_DATA).append(bundleName);
+ }
+
+ public IPath getPreferenceLocation(String bundleName, boolean create) throws IllegalStateException {
+ IPath result = getStateLocation(bundleName);
+ if (create)
+ result.toFile().mkdirs();
+ return result.append(PREFERENCES_FILE_NAME);
+ }
+
+ private void initializeLocation() throws CoreException {
+ // check if the location can be created
+ if (location.toFile().exists()) {
+ if (!location.toFile().isDirectory()) {
+ String message = NLS.bind(CommonMessages.meta_notDir, location);
+ throw new CoreException(new Status(IStatus.ERROR, IRuntimeConstants.PI_RUNTIME, IRuntimeConstants.FAILED_WRITE_METADATA, message, null));
+ }
+ }
+ //try infer the device if there isn't one (windows)
+ if (location.getDevice() == null)
+ location = new Path(location.toFile().getAbsolutePath());
+ createLocation();
+ initialized = true;
+ }
+
+ private void createLocation() throws CoreException {
+ // append on the metadata location so that the whole structure is created.
+ File file = location.append(F_META_AREA).toFile();
+ try {
+ file.mkdirs();
+ } catch (Exception e) {
+ String message = NLS.bind(CommonMessages.meta_couldNotCreate, file.getAbsolutePath());
+ throw new CoreException(new Status(IStatus.ERROR, IRuntimeConstants.PI_RUNTIME, IRuntimeConstants.FAILED_WRITE_METADATA, message, e));
+ }
+ if (!file.canWrite()) {
+ String message = NLS.bind(CommonMessages.meta_readonly, file.getAbsolutePath());
+ throw new CoreException(new Status(IStatus.ERROR, IRuntimeConstants.PI_RUNTIME, IRuntimeConstants.FAILED_WRITE_METADATA, message, null));
+ }
+ // set the log file location now that we created the data area
+ IPath path = location.append(F_META_AREA).append(F_LOG);
+ try {
+ CommonOSGiUtils.getDefault().getFrameworkLog().setFile(path.toFile(), true);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/bundles/org.eclipse.equinox.common/src/org/eclipse/core/internal/runtime/DevClassPathHelper.java b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/internal/runtime/DevClassPathHelper.java
new file mode 100644
index 0000000..cfab2ea
--- /dev/null
+++ b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/internal/runtime/DevClassPathHelper.java
@@ -0,0 +1,98 @@
+/*******************************************************************************
+ * 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.runtime;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.*;
+
+public class DevClassPathHelper {
+
+ // command line options
+ public static final String PROP_DEV = "osgi.dev"; //$NON-NLS-1$
+
+ static protected boolean inDevelopmentMode = false;
+ static protected String[] devDefaultClasspath;
+ static protected Properties devProperties = null;
+
+ static {
+ // Check the osgi.dev property to see if dev classpath entries have been defined.
+ String osgiDev = System.getProperty(PROP_DEV);
+ if (osgiDev != null) {
+ try {
+ inDevelopmentMode = true;
+ URL location = new URL(osgiDev);
+ devProperties = load(location);
+ if (devProperties != null)
+ devDefaultClasspath = getArrayFromList(devProperties.getProperty("*")); //$NON-NLS-1$
+ } catch (MalformedURLException e) {
+ devDefaultClasspath = getArrayFromList(osgiDev);
+ }
+ }
+ }
+
+ public static String[] getDevClassPath(String id) {
+ String[] result = null;
+ if (id != null && devProperties != null) {
+ String entry = devProperties.getProperty(id);
+ if (entry != null)
+ result = getArrayFromList(entry);
+ }
+ if (result == null)
+ result = devDefaultClasspath;
+ return result;
+ }
+
+ /**
+ * Returns the result of converting a list of comma-separated tokens into an array
+ *
+ * @return the array of string tokens
+ * @param prop the initial comma-separated string
+ */
+ public static String[] getArrayFromList(String prop) {
+ if (prop == null || prop.trim().equals("")) //$NON-NLS-1$
+ return new String[0];
+ Vector list = new Vector();
+ StringTokenizer tokens = new StringTokenizer(prop, ","); //$NON-NLS-1$
+ while (tokens.hasMoreTokens()) {
+ String token = tokens.nextToken().trim();
+ if (!token.equals("")) //$NON-NLS-1$
+ list.addElement(token);
+ }
+ return list.isEmpty() ? new String[0] : (String[]) list.toArray(new String[list.size()]);
+ }
+
+ public static boolean inDevelopmentMode() {
+ return inDevelopmentMode;
+ }
+
+ /*
+ * Load the given properties file
+ */
+ private static Properties load(URL url) {
+ Properties props = new Properties();
+ try {
+ InputStream is = null;
+ try {
+ is = url.openStream();
+ props.load(is);
+ } finally {
+ if (is != null)
+ is.close();
+ }
+ } catch (IOException e) {
+ // TODO consider logging here
+ }
+ return props;
+ }
+}
diff --git a/bundles/org.eclipse.equinox.common/src/org/eclipse/core/internal/runtime/FindSupport.java b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/internal/runtime/FindSupport.java
new file mode 100644
index 0000000..1a9b835
--- /dev/null
+++ b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/internal/runtime/FindSupport.java
@@ -0,0 +1,246 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 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.runtime;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Map;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.Path;
+import org.osgi.framework.Bundle;
+
+// This class provides implements the find* methods exposed on Platform.
+// It does the lookup in bundles and fragments and does the variable replacement.
+// Can only be used if OSGi is available.
+public class FindSupport {
+ // OSGI system properties
+ public static final String PROP_NL = "osgi.nl"; //$NON-NLS-1$
+ public static final String PROP_OS = "osgi.os"; //$NON-NLS-1$
+ public static final String PROP_WS = "osgi.ws"; //$NON-NLS-1$
+ public static final String PROP_ARCH = "osgi.arch"; //$NON-NLS-1$
+
+ private static String[] NL_JAR_VARIANTS = buildNLVariants(System.getProperty(PROP_NL));
+
+ private static String[] buildNLVariants(String nl) {
+ ArrayList result = new ArrayList();
+ IPath base = new Path("nl"); //$NON-NLS-1$
+
+ IPath path = new Path(nl.replace('_', '/'));
+ while (path.segmentCount() > 0) {
+ result.add(base.append(path).toString());
+ // for backwards compatibility only, don't replace the slashes
+ if (path.segmentCount() > 1)
+ result.add(base.append(path.toString().replace('/', '_')).toString());
+ path = path.removeLastSegments(1);
+ }
+
+ return (String[]) result.toArray(new String[result.size()]);
+ }
+
+ /**
+ * See doc on @link Platform#find(Bundle, IPath) Platform#find(Bundle, IPath)
+ */
+ public static URL find(Bundle bundle, IPath path) {
+ return find(bundle, path, null);
+ }
+
+ /**
+ * See doc on @link Platform#find(Bundle, IPath, Map) Platform#find(Bundle, IPath, Map)
+ */
+ public static URL find(Bundle b, IPath path, Map override) {
+ if (path == null)
+ return null;
+
+ URL result = null;
+
+ // Check for the empty or root case first
+ if (path.isEmpty() || path.isRoot()) {
+ // Watch for the root case. It will produce a new
+ // URL which is only the root directory (and not the
+ // root of this plugin).
+ result = findInPlugin(b, Path.EMPTY);
+ if (result == null)
+ result = findInFragments(b, Path.EMPTY);
+ return result;
+ }
+
+ // Now check for paths without variable substitution
+ String first = path.segment(0);
+ if (first.charAt(0) != '$') {
+ result = findInPlugin(b, path);
+ if (result == null)
+ result = findInFragments(b, path);
+ return result;
+ }
+
+ // Worry about variable substitution
+ IPath rest = path.removeFirstSegments(1);
+ if (first.equalsIgnoreCase("$nl$")) //$NON-NLS-1$
+ return findNL(b, rest, override);
+ if (first.equalsIgnoreCase("$os$")) //$NON-NLS-1$
+ return findOS(b, rest, override);
+ if (first.equalsIgnoreCase("$ws$")) //$NON-NLS-1$
+ return findWS(b, rest, override);
+ if (first.equalsIgnoreCase("$files$")) //$NON-NLS-1$
+ return null;
+
+ return null;
+ }
+
+ private static URL findOS(Bundle b, IPath path, Map override) {
+ String os = null;
+ if (override != null)
+ try {
+ // check for override
+ os = (String) override.get("$os$"); //$NON-NLS-1$
+ } catch (ClassCastException e) {
+ // just in case
+ }
+ if (os == null)
+ // use default
+ os = System.getProperty(PROP_OS);
+ if (os.length() == 0)
+ return null;
+
+ // Now do the same for osarch
+ String osArch = null;
+ if (override != null)
+ try {
+ // check for override
+ osArch = (String) override.get("$arch$"); //$NON-NLS-1$
+ } catch (ClassCastException e) {
+ // just in case
+ }
+ if (osArch == null)
+ // use default
+ osArch = System.getProperty(PROP_ARCH);
+ if (osArch.length() == 0)
+ return null;
+
+ URL result = null;
+ IPath base = new Path("os").append(os).append(osArch); //$NON-NLS-1$
+ // Keep doing this until all you have left is "os" as a path
+ while (base.segmentCount() != 1) {
+ IPath filePath = base.append(path);
+ result = findInPlugin(b, filePath);
+ if (result != null)
+ return result;
+ result = findInFragments(b, filePath);
+ if (result != null)
+ return result;
+ base = base.removeLastSegments(1);
+ }
+ // If we get to this point, we haven't found it yet.
+ // Look in the plugin and fragment root directories
+ result = findInPlugin(b, path);
+ if (result != null)
+ return result;
+ return findInFragments(b, path);
+ }
+
+ private static URL findWS(Bundle b, IPath path, Map override) {
+ String ws = null;
+ if (override != null)
+ try {
+ // check for override
+ ws = (String) override.get("$ws$"); //$NON-NLS-1$
+ } catch (ClassCastException e) {
+ // just in case
+ }
+ if (ws == null)
+ // use default
+ ws = System.getProperty(PROP_WS);
+ IPath filePath = new Path("ws").append(ws).append(path); //$NON-NLS-1$
+ // We know that there is only one segment to the ws path
+ // e.g. ws/win32
+ URL result = findInPlugin(b, filePath);
+ if (result != null)
+ return result;
+ result = findInFragments(b, filePath);
+ if (result != null)
+ return result;
+ // If we get to this point, we haven't found it yet.
+ // Look in the plugin and fragment root directories
+ result = findInPlugin(b, path);
+ if (result != null)
+ return result;
+ return findInFragments(b, path);
+ }
+
+ private static URL findNL(Bundle b, IPath path, Map override) {
+ String nl = null;
+ String[] nlVariants = null;
+ if (override != null)
+ try {
+ // check for override
+ nl = (String) override.get("$nl$"); //$NON-NLS-1$
+ } catch (ClassCastException e) {
+ // just in case
+ }
+ nlVariants = nl == null ? NL_JAR_VARIANTS : buildNLVariants(nl);
+ if (nl != null && nl.length() == 0)
+ return null;
+
+ URL result = null;
+ for (int i = 0; i < nlVariants.length; i++) {
+ IPath filePath = new Path(nlVariants[i]).append(path);
+ result = findInPlugin(b, filePath);
+ if (result != null)
+ return result;
+ result = findInFragments(b, filePath);
+ if (result != null)
+ return result;
+ }
+ // If we get to this point, we haven't found it yet.
+ // Look in the plugin and fragment root directories
+ result = findInPlugin(b, path);
+ if (result != null)
+ return result;
+ return findInFragments(b, path);
+ }
+
+ private static URL findInPlugin(Bundle b, IPath filePath) {
+ return b.getEntry(filePath.toString());
+ }
+
+ private static URL findInFragments(Bundle b, IPath filePath) {
+ Bundle[] fragments = CommonOSGiUtils.getDefault().getFragments(b);
+ if (fragments == null)
+ return null;
+
+ URL fileURL = null;
+ int i = 0;
+ while (i < fragments.length && fileURL == null) {
+ fileURL = fragments[i].getEntry(filePath.toString());
+ i++;
+ }
+ return fileURL;
+ }
+
+ /**
+ * See doc on @link Platform#openStream(Bundle, IPath, boolean) Platform#Platform#openStream(Bundle, IPath, boolean)
+ */
+ public static final InputStream openStream(Bundle bundle, IPath file, boolean localized) throws IOException {
+ URL url = null;
+ if (!localized) {
+ url = findInPlugin(bundle, file);
+ if (url == null)
+ url = findInFragments(bundle, file);
+ } else {
+ url = FindSupport.find(bundle, file);
+ }
+ if (url != null)
+ return url.openStream();
+ throw new IOException("Cannot find " + file.toString()); //$NON-NLS-1$
+ }
+}
diff --git a/bundles/org.eclipse.equinox.common/src/org/eclipse/core/internal/runtime/IAdapterFactoryExt.java b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/internal/runtime/IAdapterFactoryExt.java
new file mode 100644
index 0000000..c473980
--- /dev/null
+++ b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/internal/runtime/IAdapterFactoryExt.java
@@ -0,0 +1,31 @@
+/*******************************************************************************
+ * Copyright (c) 2005 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.runtime;
+
+import org.eclipse.core.runtime.IAdapterFactory;
+
+/**
+ * An internal interface that exposes portion of AdapterFactoryProxy functionality
+ * without the need to import the class itself.
+ */
+public interface IAdapterFactoryExt {
+
+ /**
+ * Loads the real adapter factory, but only if its associated plug-in is
+ * already loaded. Returns the real factory if it was successfully loaded.
+ * @param force if <code>true</code> the plugin providing the
+ * factory will be loaded if necessary, otherwise no plugin activations
+ * will occur.
+ */
+ public IAdapterFactory loadFactory(boolean force);
+
+ public String[] getAdapterNames();
+}
diff --git a/bundles/org.eclipse.equinox.common/src/org/eclipse/core/internal/runtime/IRuntimeConstants.java b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/internal/runtime/IRuntimeConstants.java
new file mode 100644
index 0000000..a31af5b
--- /dev/null
+++ b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/internal/runtime/IRuntimeConstants.java
@@ -0,0 +1,37 @@
+/*******************************************************************************
+ * Copyright (c) 2005 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.runtime;
+
+public interface IRuntimeConstants {
+
+ /**
+ * The unique identifier constant (value "<code>org.eclipse.core.runtime</code>")
+ * of the Core Runtime (pseudo-) plug-in.
+ */
+ public static final String PI_RUNTIME = "org.eclipse.core.runtime"; //$NON-NLS-1$
+
+ /**
+ * Name of this bundle.
+ */
+ public static final String NAME = "org.eclipse.runtime.common"; //$NON-NLS-1$
+
+ /**
+ * Status code constant (value 2) indicating an error occurred while running a plug-in.
+ */
+ public static final int PLUGIN_ERROR = 2;
+
+ /**
+ * Status code constant (value 5) indicating the platform could not write
+ * some of its metadata.
+ */
+ public static final int FAILED_WRITE_METADATA = 5;
+
+}
diff --git a/bundles/org.eclipse.equinox.common/src/org/eclipse/core/internal/runtime/ListenerList.java b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/internal/runtime/ListenerList.java
new file mode 100644
index 0000000..3b0e49e
--- /dev/null
+++ b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/internal/runtime/ListenerList.java
@@ -0,0 +1,160 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2005 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.runtime;
+
+/**
+ * This class is going to be removed soon.
+ * Please use org.eclipse.core.runtime.ListenerList
+ *
+ * @deprecated
+ */
+// TODO remove this class
+public class ListenerList {
+
+ /**
+ * The empty array singleton instance.
+ */
+ private static final Object[] EmptyArray = new Object[0];
+
+ /**
+ * Mode constant (value 0) indicating that listeners should be considered
+ * the <a href="#same">same</a> if they are equal.
+ */
+ public static final int EQUALITY = 0;
+
+ /**
+ * Mode constant (value 1) indicating that listeners should be considered
+ * the <a href="#same">same</a> if they are identical.
+ */
+ public static final int IDENTITY = 1;
+
+ /**
+ * Indicates the comparison mode used to determine if two
+ * listeners are equivalent
+ */
+ private final boolean identity;
+
+ /**
+ * The list of listeners. Initially empty but initialized
+ * to an array of size capacity the first time a listener is added.
+ * Maintains invariant: listeners != null
+ */
+ private volatile Object[] listeners = EmptyArray;
+
+ /**
+ * Creates a listener list in which listeners are compared using equality.
+ */
+ public ListenerList() {
+ this(EQUALITY);
+ }
+
+ /**
+ * Creates a listener list using the provided comparison mode.
+ *
+ * @param mode The mode used to determine if listeners are the <a href="#same">same</a>.
+ */
+ public ListenerList(int mode) {
+ if (mode != EQUALITY && mode != IDENTITY)
+ throw new IllegalArgumentException();
+ this.identity = mode == IDENTITY;
+ }
+
+ /**
+ * Adds a listener to this list. This method has no effect if the <a href="#same">same</a>
+ * listener is already registered.
+ *
+ * @param listener the listener to add
+ */
+ public synchronized void add(Object listener) {
+ // This method is synchronized to protect against multiple threads adding
+ // or removing listeners concurrently. This does not block concurrent readers.
+ if (listener == null)
+ throw new IllegalArgumentException();
+ // check for duplicates
+ final int oldSize = listeners.length;
+ for (int i = 0; i < oldSize; ++i) {
+ Object listener2 = listeners[i];
+ if (identity ? listener == listener2 : listener.equals(listener2))
+ return;
+ }
+ // Thread safety: create new array to avoid affecting concurrent readers
+ Object[] newListeners = new Object[oldSize + 1];
+ System.arraycopy(listeners, 0, newListeners, 0, oldSize);
+ newListeners[oldSize] = listener;
+ //atomic assignment
+ this.listeners = newListeners;
+ }
+
+ /**
+ * Returns an array containing all the registered listeners.
+ * The resulting array is unaffected by subsequent adds or removes.
+ * If there are no listeners registered, the result is an empty array.
+ * Use this method when notifying listeners, so that any modifications
+ * to the listener list during the notification will have no effect on
+ * the notification itself.
+ * <p>
+ * Note: Callers of this method <b>must not</b> modify the returned array.
+ *
+ * @return the list of registered listeners
+ */
+ public Object[] getListeners() {
+ return listeners;
+ }
+
+ /**
+ * Returns whether this listener list is empty.
+ *
+ * @return <code>true</code> if there are no registered listeners, and
+ * <code>false</code> otherwise
+ */
+ public boolean isEmpty() {
+ return listeners.length == 0;
+ }
+
+ /**
+ * Removes a listener from this list. Has no effect if the <a href="#same">same</a>
+ * listener was not already registered.
+ *
+ * @param listener the listener to remove
+ */
+ public synchronized void remove(Object listener) {
+ // This method is synchronized to protect against multiple threads adding
+ // or removing listeners concurrently. This does not block concurrent readers.
+ if (listener == null)
+ throw new IllegalArgumentException();
+ int oldSize = listeners.length;
+ for (int i = 0; i < oldSize; ++i) {
+ Object listener2 = listeners[i];
+ if (identity ? listener == listener2 : listener.equals(listener2)) {
+ if (oldSize == 1) {
+ listeners = EmptyArray;
+ } else {
+ // Thread safety: create new array to avoid affecting concurrent readers
+ Object[] newListeners = new Object[oldSize - 1];
+ System.arraycopy(listeners, 0, newListeners, 0, i);
+ System.arraycopy(listeners, i + 1, newListeners, i, oldSize - i - 1);
+ //atomic assignment to field
+ this.listeners = newListeners;
+ }
+ return;
+ }
+ }
+ }
+
+ /**
+ * Returns the number of registered listeners.
+ *
+ * @return the number of registered listeners
+ */
+ public int size() {
+ return listeners.length;
+ }
+}
diff --git a/bundles/org.eclipse.equinox.common/src/org/eclipse/core/internal/runtime/MessageResourceBundle.java b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/internal/runtime/MessageResourceBundle.java
new file mode 100644
index 0000000..4f7564e
--- /dev/null
+++ b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/internal/runtime/MessageResourceBundle.java
@@ -0,0 +1,225 @@
+/*******************************************************************************
+ * Copyright (c) 2005 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.runtime;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.*;
+import org.eclipse.core.runtime.Status;
+
+/**
+ * This class is used for NLS processing in plugins that can't depend on OSGi.
+ *
+ * Class responsible for loading message values from a property file
+ * and assigning them directly to the fields of a messages class.
+ *
+ * Copied from org.eclipse.osgi.framework.internal.core.MessageResourceBundle as of August 30, 2005.
+ * Debug code removed; logging is done via RuntimeLog.
+ *
+ * @since org.eclipse.equinox.common 1.0
+ */
+public class MessageResourceBundle {
+ /**
+ * Class which sub-classes java.util.Properties and uses the #put method
+ * to set field values rather than storing the values in the table.
+ *
+ * @since 3.1
+ */
+ private static class MessagesProperties extends Properties {
+
+ private static final int MOD_EXPECTED = Modifier.PUBLIC | Modifier.STATIC;
+ private static final int MOD_MASK = MOD_EXPECTED | Modifier.FINAL;
+ private static final long serialVersionUID = 1L;
+
+ private final String bundleName;
+ private final Map fields;
+ private final boolean isAccessible;
+
+ public MessagesProperties(Map fieldMap, String bundleName, boolean isAccessible) {
+ super();
+ this.fields = fieldMap;
+ this.bundleName = bundleName;
+ this.isAccessible = isAccessible;
+ }
+
+ /* (non-Javadoc)
+ * @see java.util.Hashtable#put(java.lang.Object, java.lang.Object)
+ */
+ public synchronized Object put(Object key, Object value) {
+ Object fieldObject = fields.put(key, ASSIGNED);
+ // if already assigned, there is nothing to do
+ if (fieldObject == ASSIGNED)
+ return null;
+ if (fieldObject == null) {
+ final String msg = "NLS unused message: " + key + " in: " + bundleName;//$NON-NLS-1$ //$NON-NLS-2$
+ log(SEVERITY_WARNING, msg, null);
+ return null;
+ }
+ final Field field = (Field) fieldObject;
+ //can only set value of public static non-final fields
+ if ((field.getModifiers() & MOD_MASK) != MOD_EXPECTED)
+ return null;
+ try {
+ // Check to see if we are allowed to modify the field. If we aren't (for instance
+ // if the class is not public) then change the accessible attribute of the field
+ // before trying to set the value.
+ if (!isAccessible)
+ makeAccessible(field);
+ // Set the value into the field. We should never get an exception here because
+ // we know we have a public static non-final field. If we do get an exception, silently
+ // log it and continue. This means that the field will (most likely) be un-initialized and
+ // will fail later in the code and if so then we will see both the NPE and this error.
+ field.set(null, value);
+ } catch (Exception e) {
+ log(SEVERITY_ERROR, "Exception setting field value.", e); //$NON-NLS-1$
+ }
+ return null;
+ }
+ }
+
+ /**
+ * This object is assigned to the value of a field map to indicate
+ * that a translated message has already been assigned to that field.
+ */
+ static final Object ASSIGNED = new Object();
+
+ private static final String EXTENSION = ".properties"; //$NON-NLS-1$
+
+ private static String[] nlSuffixes;
+
+ static int SEVERITY_ERROR = 0x04;
+
+ static int SEVERITY_WARNING = 0x02;
+
+ /*
+ * Build an array of property files to search. The returned array contains
+ * the property fields in order from most specific to most generic.
+ * So, in the FR_fr locale, it will return file_fr_FR.properties, then
+ * file_fr.properties, and finally file.properties.
+ */
+ private static String[] buildVariants(String root) {
+ if (nlSuffixes == null) {
+ //build list of suffixes for loading resource bundles
+ String nl = Locale.getDefault().toString();
+ ArrayList result = new ArrayList(4);
+ int lastSeparator;
+ while (true) {
+ result.add('_' + nl + EXTENSION);
+ lastSeparator = nl.lastIndexOf('_');
+ if (lastSeparator == -1)
+ break;
+ nl = nl.substring(0, lastSeparator);
+ }
+ //add the empty suffix last (most general)
+ result.add(EXTENSION);
+ nlSuffixes = (String[]) result.toArray(new String[result.size()]);
+ }
+ root = root.replace('.', '/');
+ String[] variants = new String[nlSuffixes.length];
+ for (int i = 0; i < variants.length; i++)
+ variants[i] = root + nlSuffixes[i];
+ return variants;
+ }
+
+ /*
+ * Change the accessibility of the specified field so we can set its value
+ * to be the appropriate message string.
+ */
+ static void makeAccessible(final Field field) {
+ if (System.getSecurityManager() == null) {
+ field.setAccessible(true);
+ } else {
+ AccessController.doPrivileged(new PrivilegedAction() {
+ public Object run() {
+ field.setAccessible(true);
+ return null;
+ }
+ });
+ }
+ }
+
+ private static void computeMissingMessages(String bundleName, Class clazz, Map fieldMap, Field[] fieldArray, boolean isAccessible) {
+ // iterate over the fields in the class to make sure that there aren't any empty ones
+ final int MOD_EXPECTED = Modifier.PUBLIC | Modifier.STATIC;
+ final int MOD_MASK = MOD_EXPECTED | Modifier.FINAL;
+ final int numFields = fieldArray.length;
+ for (int i = 0; i < numFields; i++) {
+ Field field = fieldArray[i];
+ if ((field.getModifiers() & MOD_MASK) != MOD_EXPECTED)
+ continue;
+ //if the field has a a value assigned, there is nothing to do
+ if (fieldMap.get(field.getName()) == ASSIGNED)
+ continue;
+ try {
+ // Set a value for this empty field. We should never get an exception here because
+ // we know we have a public static non-final field. If we do get an exception, silently
+ // log it and continue. This means that the field will (most likely) be un-initialized and
+ // will fail later in the code and if so then we will see both the NPE and this error.
+ String value = "NLS missing message: " + field.getName() + " in: " + bundleName; //$NON-NLS-1$ //$NON-NLS-2$
+ log(SEVERITY_WARNING, value, null);
+ if (!isAccessible)
+ makeAccessible(field);
+ field.set(null, value);
+ } catch (Exception e) {
+ log(SEVERITY_ERROR, "Error setting the missing message value for: " + field.getName(), e); //$NON-NLS-1$
+ }
+ }
+ }
+
+ /**
+ * Load the given resource bundle using the specified class loader.
+ */
+ public static void load(final String bundleName, Class clazz) {
+ final Field[] fieldArray = clazz.getDeclaredFields();
+ ClassLoader loader = clazz.getClassLoader();
+
+ boolean isAccessible = (clazz.getModifiers() & Modifier.PUBLIC) != 0;
+
+ //build a map of field names to Field objects
+ final int len = fieldArray.length;
+ Map fields = new HashMap(len * 2);
+ for (int i = 0; i < len; i++)
+ fields.put(fieldArray[i].getName(), fieldArray[i]);
+
+ // search the variants from most specific to most general, since
+ // the MessagesProperties.put method will mark assigned fields
+ // to prevent them from being assigned twice
+ final String[] variants = buildVariants(bundleName);
+ for (int i = 0; i < variants.length; i++) {
+ final InputStream input = loader.getResourceAsStream(variants[i]);
+ if (input == null)
+ continue;
+ try {
+ final MessagesProperties properties = new MessagesProperties(fields, bundleName, isAccessible);
+ properties.load(input);
+ } catch (IOException e) {
+ log(SEVERITY_ERROR, "Error loading " + variants[i], e); //$NON-NLS-1$
+ } finally {
+ if (input != null)
+ try {
+ input.close();
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+ }
+ computeMissingMessages(bundleName, clazz, fields, fieldArray, isAccessible);
+ }
+
+ private static void log(int severity, String msg, Exception e) {
+ RuntimeLog.log(new Status(severity, IRuntimeConstants.PI_RUNTIME, 0, msg, e));
+ }
+
+}
\ No newline at end of file
diff --git a/bundles/org.eclipse.equinox.common/src/org/eclipse/core/internal/runtime/MetaDataKeeper.java b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/internal/runtime/MetaDataKeeper.java
new file mode 100644
index 0000000..164f318
--- /dev/null
+++ b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/internal/runtime/MetaDataKeeper.java
@@ -0,0 +1,37 @@
+/*******************************************************************************
+ * Copyright (c) 2005 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.runtime;
+
+import org.eclipse.core.internal.runtime.DataArea;
+
+/**
+ * The class contains a set of utilities working platform metadata area.
+ * This class can only be used if OSGi plugin is available.
+ *
+ * Copied from InternalPlatform as of August 30, 2005.
+ * @since org.eclipse.equinox.common 1.0
+ */
+public class MetaDataKeeper {
+
+ private static DataArea metaArea = null;
+
+ /**
+ * Returns the object which defines the location and organization
+ * of the platform's meta area.
+ */
+ public static DataArea getMetaArea() {
+ if (metaArea != null)
+ return metaArea;
+
+ metaArea = new DataArea();
+ return metaArea;
+ }
+}
diff --git a/bundles/org.eclipse.equinox.common/src/org/eclipse/core/internal/runtime/ReferenceHashSet.java b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/internal/runtime/ReferenceHashSet.java
new file mode 100644
index 0000000..eab95b9
--- /dev/null
+++ b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/internal/runtime/ReferenceHashSet.java
@@ -0,0 +1,323 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2005 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.runtime;
+
+import java.lang.ref.*;
+
+/**
+ * A hashset whose values can be garbage collected.
+ * This API is EXPERIMENTAL and provided as early access.
+ * @since 3.1
+ */
+public class ReferenceHashSet {
+
+ private interface HashedReference {
+ int hashCode();
+
+ Object get();
+ }
+
+ private class HashableWeakReference extends WeakReference implements HashedReference {
+ public int hashCode;
+
+ public HashableWeakReference(Object referent, ReferenceQueue queue) {
+ super(referent, queue);
+ this.hashCode = referent.hashCode();
+ }
+
+ public boolean equals(Object obj) {
+ if (!(obj instanceof HashableWeakReference))
+ return false;
+ Object referent = get();
+ Object other = ((HashableWeakReference) obj).get();
+ if (referent == null)
+ return other == null;
+ return referent.equals(other);
+ }
+
+ public int hashCode() {
+ return this.hashCode;
+ }
+
+ public String toString() {
+ Object referent = get();
+ if (referent == null)
+ return "[hashCode=" + this.hashCode + "] <referent was garbage collected>"; //$NON-NLS-1$ //$NON-NLS-2$
+ return "[hashCode=" + this.hashCode + "] " + referent.toString(); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ }
+
+ private class HashableSoftReference extends SoftReference implements HashedReference {
+ public int hashCode;
+
+ public HashableSoftReference(Object referent, ReferenceQueue queue) {
+ super(referent, queue);
+ this.hashCode = referent.hashCode();
+ }
+
+ public boolean equals(Object obj) {
+ if (!(obj instanceof HashableWeakReference))
+ return false;
+ Object referent = get();
+ Object other = ((HashableWeakReference) obj).get();
+ if (referent == null)
+ return other == null;
+ return referent.equals(other);
+ }
+
+ public int hashCode() {
+ return this.hashCode;
+ }
+
+ public String toString() {
+ Object referent = get();
+ if (referent == null)
+ return "[hashCode=" + this.hashCode + "] <referent was garbage collected>"; //$NON-NLS-1$ //$NON-NLS-2$
+ return "[hashCode=" + this.hashCode + "] " + referent.toString(); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ }
+
+ private class StrongReference implements HashedReference {
+ private Object referent;
+
+ public StrongReference(Object referent, ReferenceQueue queue) {
+ this.referent = referent;
+ }
+
+ public int hashCode() {
+ return referent.hashCode();
+ }
+
+ public Object get() {
+ return referent;
+ }
+
+ public boolean equals(Object obj) {
+ return referent.equals(obj);
+ }
+ }
+
+ HashedReference[] values;
+
+ public int elementSize; // number of elements in the table
+
+ int threshold;
+
+ ReferenceQueue referenceQueue = new ReferenceQueue();
+
+ public ReferenceHashSet() {
+ this(5);
+ }
+
+ public ReferenceHashSet(int size) {
+ this.elementSize = 0;
+ this.threshold = size; // size represents the expected
+ // number of elements
+ int extraRoom = (int) (size * 1.75f);
+ if (this.threshold == extraRoom)
+ extraRoom++;
+ this.values = new HashedReference[extraRoom];
+ }
+
+ /**
+ * Constant indicating that hard references should be used.
+ */
+ final public static int HARD = 0;
+
+ /**
+ * Constant indiciating that soft references should be used.
+ */
+ final public static int SOFT = 1;
+
+ /**
+ * Constant indicating that weak references should be used.
+ */
+ final public static int WEAK = 2;
+
+ private HashedReference toReference(int type, Object referent) {
+ switch (type) {
+ case HARD :
+ return new StrongReference(referent, referenceQueue);
+ case SOFT :
+ return new HashableSoftReference(referent, referenceQueue);
+ case WEAK :
+ return new HashableWeakReference(referent, referenceQueue);
+ default :
+ throw new Error();
+ }
+ }
+
+ /*
+ * Adds the given object to this set. If an object that is equals to the
+ * given object already exists, do nothing. Returns the existing object or
+ * the new object if not found.
+ */
+ public Object add(Object obj, int referenceType) {
+ cleanupGarbageCollectedValues();
+ int index = (obj.hashCode() & 0x7FFFFFFF) % this.values.length;
+ HashedReference currentValue;
+ while ((currentValue = this.values[index]) != null) {
+ Object referent;
+ if (obj.equals(referent = currentValue.get())) {
+ return referent;
+ }
+ index = (index + 1) % this.values.length;
+ }
+ this.values[index] = toReference(referenceType, obj);
+
+ // assumes the threshold is never equal to the size of the table
+ if (++this.elementSize > this.threshold)
+ rehash();
+
+ return obj;
+ }
+
+ private void addValue(HashedReference value) {
+ Object obj = value.get();
+ if (obj == null)
+ return;
+ int valuesLength = this.values.length;
+ int index = (value.hashCode() & 0x7FFFFFFF) % valuesLength;
+ HashedReference currentValue;
+ while ((currentValue = this.values[index]) != null) {
+ if (obj.equals(currentValue.get())) {
+ return;
+ }
+ index = (index + 1) % valuesLength;
+ }
+ this.values[index] = value;
+
+ // assumes the threshold is never equal to the size of the table
+ if (++this.elementSize > this.threshold)
+ rehash();
+ }
+
+ private void cleanupGarbageCollectedValues() {
+ HashedReference toBeRemoved;
+ while ((toBeRemoved = (HashedReference) this.referenceQueue.poll()) != null) {
+ int hashCode = toBeRemoved.hashCode();
+ int valuesLength = this.values.length;
+ int index = (hashCode & 0x7FFFFFFF) % valuesLength;
+ HashedReference currentValue;
+ while ((currentValue = this.values[index]) != null) {
+ if (currentValue == toBeRemoved) {
+ // replace the value at index with the last value with the
+ // same hash
+ int sameHash = index;
+ int current;
+ while ((currentValue = this.values[current = (sameHash + 1) % valuesLength]) != null && currentValue.hashCode() == hashCode)
+ sameHash = current;
+ this.values[index] = this.values[sameHash];
+ this.values[sameHash] = null;
+ this.elementSize--;
+ break;
+ }
+ index = (index + 1) % valuesLength;
+ }
+ }
+ }
+
+ public boolean contains(Object obj) {
+ return get(obj) != null;
+ }
+
+ /*
+ * Return the object that is in this set and that is equals to the given
+ * object. Return null if not found.
+ */
+ public Object get(Object obj) {
+ cleanupGarbageCollectedValues();
+ int valuesLength = this.values.length;
+ int index = (obj.hashCode() & 0x7FFFFFFF) % valuesLength;
+ HashedReference currentValue;
+ while ((currentValue = this.values[index]) != null) {
+ Object referent;
+ if (obj.equals(referent = currentValue.get())) {
+ return referent;
+ }
+ index = (index + 1) % valuesLength;
+ }
+ return null;
+ }
+
+ private void rehash() {
+ ReferenceHashSet newHashSet = new ReferenceHashSet(this.elementSize * 2); // double the number of expected elements
+ newHashSet.referenceQueue = this.referenceQueue;
+ HashedReference currentValue;
+ for (int i = 0, length = this.values.length; i < length; i++)
+ if ((currentValue = this.values[i]) != null)
+ newHashSet.addValue(currentValue);
+
+ this.values = newHashSet.values;
+ this.threshold = newHashSet.threshold;
+ this.elementSize = newHashSet.elementSize;
+ }
+
+ /*
+ * Removes the object that is in this set and that is equals to the given
+ * object. Return the object that was in the set, or null if not found.
+ */
+ public Object remove(Object obj) {
+ cleanupGarbageCollectedValues();
+ int valuesLength = this.values.length;
+ int index = (obj.hashCode() & 0x7FFFFFFF) % valuesLength;
+ HashedReference currentValue;
+ while ((currentValue = this.values[index]) != null) {
+ Object referent;
+ if (obj.equals(referent = currentValue.get())) {
+ this.elementSize--;
+ this.values[index] = null;
+ rehash();
+ return referent;
+ }
+ index = (index + 1) % valuesLength;
+ }
+ return null;
+ }
+
+ public int size() {
+ return this.elementSize;
+ }
+
+ public String toString() {
+ StringBuffer buffer = new StringBuffer("{"); //$NON-NLS-1$
+ for (int i = 0, length = this.values.length; i < length; i++) {
+ HashedReference value = this.values[i];
+ if (value != null) {
+ Object ref = value.get();
+ if (ref != null) {
+ buffer.append(ref.toString());
+ buffer.append(", "); //$NON-NLS-1$
+ }
+ }
+ }
+ buffer.append("}"); //$NON-NLS-1$
+ return buffer.toString();
+ }
+
+ public Object[] toArray() {
+ cleanupGarbageCollectedValues();
+ Object[] result = new Object[elementSize];
+ int resultSize = 0;
+ for (int i = 0; i < values.length; i++) {
+ if (values[i] == null)
+ continue;
+ Object tmp = values[i].get();
+ if (tmp != null)
+ result[resultSize++] = tmp;
+ }
+ if (result.length == resultSize)
+ return result;
+ Object[] finalResult = new Object[resultSize];
+ System.arraycopy(result, 0, finalResult, 0, resultSize);
+ return finalResult;
+ }
+}
diff --git a/bundles/org.eclipse.equinox.common/src/org/eclipse/core/internal/runtime/ResourceTranslator.java b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/internal/runtime/ResourceTranslator.java
new file mode 100644
index 0000000..f12aebc
--- /dev/null
+++ b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/internal/runtime/ResourceTranslator.java
@@ -0,0 +1,134 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 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.runtime;
+
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.*;
+import org.eclipse.osgi.util.ManifestElement;
+import org.osgi.framework.*;
+
+/**
+ * This class can only be used if OSGi plugin is available.
+ */
+public class ResourceTranslator {
+ private static final String KEY_PREFIX = "%"; //$NON-NLS-1$
+ private static final String KEY_DOUBLE_PREFIX = "%%"; //$NON-NLS-1$
+
+ public static String getResourceString(Bundle bundle, String value) {
+ return getResourceString(bundle, value, null);
+ }
+
+ public static String getResourceString(Bundle bundle, String value, ResourceBundle resourceBundle) {
+ String s = value.trim();
+ if (!s.startsWith(KEY_PREFIX, 0))
+ return s;
+ if (s.startsWith(KEY_DOUBLE_PREFIX, 0))
+ return s.substring(1);
+
+ int ix = s.indexOf(' ');
+ String key = ix == -1 ? s : s.substring(0, ix);
+ String dflt = ix == -1 ? s : s.substring(ix + 1);
+
+ if (resourceBundle == null && bundle != null) {
+ try {
+ resourceBundle = getResourceBundle(bundle);
+ } catch (MissingResourceException e) {
+ // just return the default (dflt)
+ }
+ }
+
+ if (resourceBundle == null)
+ return dflt;
+
+ try {
+ return resourceBundle.getString(key.substring(1));
+ } catch (MissingResourceException e) {
+ //this will avoid requiring a bundle access on the next lookup
+ return dflt;
+ }
+ }
+
+ public static ResourceBundle getResourceBundle(Bundle bundle) throws MissingResourceException {
+ if (hasRuntime21(bundle))
+ return ResourceBundle.getBundle("plugin", Locale.getDefault(), createTempClassloader(bundle)); //$NON-NLS-1$
+ return CommonOSGiUtils.getDefault().getLocalization(bundle, null);
+ }
+
+ private static boolean hasRuntime21(Bundle b) {
+ try {
+ ManifestElement[] prereqs = ManifestElement.parseHeader(Constants.REQUIRE_BUNDLE, (String) b.getHeaders("").get(Constants.REQUIRE_BUNDLE)); //$NON-NLS-1$
+ if (prereqs == null)
+ return false;
+ for (int i = 0; i < prereqs.length; i++) {
+ if ("2.1".equals(prereqs[i].getAttribute(Constants.BUNDLE_VERSION_ATTRIBUTE)) && "org.eclipse.core.runtime".equals(prereqs[i].getValue())) { //$NON-NLS-1$//$NON-NLS-2$
+ return true;
+ }
+ }
+ } catch (BundleException e) {
+ return false;
+ }
+ return false;
+ }
+
+ private static ClassLoader createTempClassloader(Bundle b) {
+ ArrayList classpath = new ArrayList();
+ addClasspathEntries(b, classpath);
+ addBundleRoot(b, classpath);
+ addDevEntries(b, classpath);
+ addFragments(b, classpath);
+ URL[] urls = new URL[classpath.size()];
+ return new URLClassLoader((URL[]) classpath.toArray(urls));
+ }
+
+ private static void addFragments(Bundle host, ArrayList classpath) {
+ Bundle[] fragments = CommonOSGiUtils.getDefault().getFragments(host);
+ if (fragments == null)
+ return;
+
+ for (int i = 0; i < fragments.length; i++) {
+ addClasspathEntries(fragments[i], classpath);
+ addDevEntries(fragments[i], classpath);
+ }
+ }
+
+ private static void addClasspathEntries(Bundle b, ArrayList classpath) {
+ ManifestElement[] classpathElements;
+ try {
+ classpathElements = ManifestElement.parseHeader(Constants.BUNDLE_CLASSPATH, (String) b.getHeaders("").get(Constants.BUNDLE_CLASSPATH)); //$NON-NLS-1$
+ if (classpathElements == null)
+ return;
+ for (int i = 0; i < classpathElements.length; i++) {
+ URL classpathEntry = b.getEntry(classpathElements[i].getValue());
+ if (classpathEntry != null)
+ classpath.add(classpathEntry);
+ }
+ } catch (BundleException e) {
+ //ignore
+ }
+ }
+
+ private static void addBundleRoot(Bundle b, ArrayList classpath) {
+ classpath.add(b.getEntry("/")); //$NON-NLS-1$
+ }
+
+ private static void addDevEntries(Bundle b, ArrayList classpath) {
+ if (!DevClassPathHelper.inDevelopmentMode())
+ return;
+
+ String[] binaryPaths = DevClassPathHelper.getDevClassPath(b.getSymbolicName());
+ for (int i = 0; i < binaryPaths.length; i++) {
+ URL classpathEntry = b.getEntry(binaryPaths[i]);
+ if (classpathEntry != null)
+ classpath.add(classpathEntry);
+ }
+ }
+}
diff --git a/bundles/org.eclipse.equinox.common/src/org/eclipse/core/internal/runtime/RuntimeLog.java b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/internal/runtime/RuntimeLog.java
new file mode 100644
index 0000000..0d7be58
--- /dev/null
+++ b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/internal/runtime/RuntimeLog.java
@@ -0,0 +1,93 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2005 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.runtime;
+
+import java.util.ArrayList;
+import org.eclipse.core.runtime.*;
+
+/**
+ * This log infrastructure was split from the InternalPlatform.
+ *
+ * @since org.eclipse.equinox.common 1.0
+ */
+public final class RuntimeLog {
+
+ private static ArrayList logListeners = new ArrayList(5);
+
+ /**
+ * @see Platform#addLogListener(ILogListener)
+ */
+ public static void addLogListener(ILogListener listener) {
+ synchronized (logListeners) {
+ // replace if already exists (Set behaviour but we use an array
+ // since we want to retain order)
+ logListeners.remove(listener);
+ logListeners.add(listener);
+ }
+ }
+
+ /**
+ * @see Platform#removeLogListener(ILogListener)
+ */
+ public static void removeLogListener(ILogListener listener) {
+ synchronized (logListeners) {
+ logListeners.remove(listener);
+ }
+ }
+
+ /**
+ * Checks if the given listener is present
+ */
+ public static boolean contains(ILogListener listener) {
+ synchronized (logListeners) {
+ return logListeners.contains(listener);
+ }
+ }
+
+ /**
+ * Notifies all listeners of the platform log.
+ */
+ public static void log(final IStatus status) {
+ // create array to avoid concurrent access
+ ILogListener[] listeners;
+ synchronized (logListeners) {
+ listeners = (ILogListener[]) logListeners.toArray(new ILogListener[logListeners.size()]);
+ }
+ for (int i = 0; i < listeners.length; i++) {
+ try {
+ listeners[i].logging(status, IRuntimeConstants.PI_RUNTIME);
+ } catch (Exception e) {
+ handleException(e);
+ } catch (LinkageError e) {
+ handleException(e);
+ }
+ }
+ }
+
+ private static void handleException(Throwable e) {
+ if (!(e instanceof OperationCanceledException)) {
+ // Got a error while logging. Don't try to log again, just put it into stderr
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Helps determine if any listeners are registered with the logging mechanism.
+ * @return true if no listeners are registered
+ */
+ public static boolean isEmpty() {
+ synchronized (logListeners) {
+ return (logListeners.size() == 0);
+ }
+ }
+
+}
diff --git a/bundles/org.eclipse.equinox.common/src/org/eclipse/core/internal/runtime/commonMessages.properties b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/internal/runtime/commonMessages.properties
new file mode 100644
index 0000000..61c1a81
--- /dev/null
+++ b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/internal/runtime/commonMessages.properties
@@ -0,0 +1,42 @@
+###############################################################################
+# Copyright (c) 2000, 2005 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
+###############################################################################
+### Common runtime plugin messages
+
+ok = OK
+
+### metadata
+meta_couldNotCreate = Error trying to create the platform metadata area: {0}.
+meta_instanceDataUnspecified = The instance data location has not been specified yet.
+meta_noDataModeSpecified = No instance data can be specified.
+meta_notDir = Specified platform location \"{0}\" is not a directory.
+meta_readonly = The platform metadata area could not be written: {0}. By default the platform writes its content\nunder the current working directory when the platform is launched. Use the -data parameter to\nspecify a different content area for the platform.
+meta_pluginProblems = Problems occurred when invoking code from plug-in: \"{0}\".
+
+### parsing/resolve
+parse_doubleSeparatorVersion=Plug-in version identifier, \"{0}\", must not contain two consecutive separator characters.
+parse_emptyPluginVersion=A plug-in version identifier must be non-empty.
+parse_fourElementPluginVersion=Plug-in version identifier, \"{0}\", can contain a maximum of four components.
+parse_numericMajorComponent=The major (1st) component of plug-in version identifier, \"{0}\", must be numeric.
+parse_numericMinorComponent=The minor (2nd) component of plug-in version identifier, \"{0}\", must be numeric.
+parse_numericServiceComponent=The service (3rd) component of plug-in version identifier, \"{0}\", must be numeric.
+parse_oneElementPluginVersion=Plug-in version identifier, \"{0}\", must contain at least one component.
+parse_postiveMajor=Plug-in version identifier, \"{0}\", must have a positive major (1st) component.
+parse_postiveMinor=Plug-in version identifier, \"{0}\", must have a positive minor (2nd) component.
+parse_postiveService=Plug-in version identifier, \"{0}\", must have a positive service (3rd) component.
+parse_separatorEndVersion=Plug-in version identifier, \"{0}\", must not end with a separator character.
+parse_separatorStartVersion=Plug-in version identifier, \"{0}\", must not start with a separator character.
+
+#FileMananger messages
+fileManager_cannotLock = Unable to create lock manager.
+fileManager_couldNotSave = Could not save file table.
+fileManager_updateFailed = File update failed on one or more files.
+fileManager_illegalInReadOnlyMode = Cannot perform operation while in read-only mode.
+fileManager_notOpen = Manager is not opened.
diff --git a/bundles/org.eclipse.equinox.common/src/org/eclipse/core/internal/runtime/package.html b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/internal/runtime/package.html
new file mode 100644
index 0000000..0898ea2
--- /dev/null
+++ b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/internal/runtime/package.html
@@ -0,0 +1,24 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+<html>
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+ <title>Package-level Javadoc</title>
+</head>
+<body>
+Provides core support for the runtime.
+<h2>
+Package Specification</h2>
+This package contains some classes that require OSGi presence. If OSGi plugin is not present in
+the platform, class loader errors will be raised if those classes get activated. The classes are:
+<UL>
+<LI>FindSupport
+<LI>DataArea
+<LI>MetaDataKeeper
+<LI>ResourceTranslator
+<LI>CommonRuntimeBundle
+<LI>CommonOSGiUtils
+</UL>
+
+
+</body>
+</html>
diff --git a/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/Assert.java b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/Assert.java
new file mode 100644
index 0000000..1ecef96
--- /dev/null
+++ b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/Assert.java
@@ -0,0 +1,110 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 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.runtime;
+
+/**
+ * <code>Assert</code> is useful for for embedding runtime sanity checks
+ * in code.
+ * The predicate methods all test a condition and throw some
+ * type of unchecked exception if the condition does not hold.
+ * <p>
+ * Assertion failure exceptions, like most runtime exceptions, are
+ * thrown when something is misbehaving. Assertion failures are invariably
+ * unspecified behavior; consequently, clients should never rely on
+ * these being thrown (and certainly should not being catching them
+ * specifically).
+ * </p>
+ */
+public final class Assert {
+ /* This class is not intended to be instantiated. */
+ private Assert() {
+ // not allowed
+ }
+
+ /** Asserts that an argument is legal. If the given boolean is
+ * not <code>true</code>, an <code>IllegalArgumentException</code>
+ * is thrown.
+ *
+ * @param expression the outcode of the check
+ * @return <code>true</code> if the check passes (does not return
+ * if the check fails)
+ * @exception IllegalArgumentException if the legality test failed
+ */
+ public static boolean isLegal(boolean expression) {
+ return isLegal(expression, ""); //$NON-NLS-1$
+ }
+
+ /** Asserts that an argument is legal. If the given boolean is
+ * not <code>true</code>, an <code>IllegalArgumentException</code>
+ * is thrown.
+ * The given message is included in that exception, to aid debugging.
+ *
+ * @param expression the outcode of the check
+ * @param message the message to include in the exception
+ * @return <code>true</code> if the check passes (does not return
+ * if the check fails)
+ * @exception IllegalArgumentException if the legality test failed
+ */
+ public static boolean isLegal(boolean expression, String message) {
+ if (!expression)
+ throw new IllegalArgumentException(message);
+ return expression;
+ }
+
+ /** Asserts that the given object is not <code>null</code>. If this
+ * is not the case, some kind of unchecked exception is thrown.
+ *
+ * @param object the value to test
+ * @exception IllegalArgumentException if the object is <code>null</code>
+ */
+ public static void isNotNull(Object object) {
+ isNotNull(object, ""); //$NON-NLS-1$
+ }
+
+ /** Asserts that the given object is not <code>null</code>. If this
+ * is not the case, some kind of unchecked exception is thrown.
+ * The given message is included in that exception, to aid debugging.
+ *
+ * @param object the value to test
+ * @param message the message to include in the exception
+ * @exception IllegalArgumentException if the object is <code>null</code>
+ */
+ public static void isNotNull(Object object, String message) {
+ if (object == null)
+ throw new AssertionFailedException("null argument:" + message); //$NON-NLS-1$
+ }
+
+ /** Asserts that the given boolean is <code>true</code>. If this
+ * is not the case, some kind of unchecked exception is thrown.
+ *
+ * @param expression the outcode of the check
+ * @return <code>true</code> if the check passes (does not return
+ * if the check fails)
+ */
+ public static boolean isTrue(boolean expression) {
+ return isTrue(expression, ""); //$NON-NLS-1$
+ }
+
+ /** Asserts that the given boolean is <code>true</code>. If this
+ * is not the case, some kind of unchecked exception is thrown.
+ * The given message is included in that exception, to aid debugging.
+ *
+ * @param expression the outcode of the check
+ * @param message the message to include in the exception
+ * @return <code>true</code> if the check passes (does not return
+ * if the check fails)
+ */
+ public static boolean isTrue(boolean expression, String message) {
+ if (!expression)
+ throw new AssertionFailedException("assertion failed: " + message); //$NON-NLS-1$
+ return expression;
+ }
+}
diff --git a/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/AssertionFailedException.java b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/AssertionFailedException.java
new file mode 100644
index 0000000..8f3cbcb
--- /dev/null
+++ b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/AssertionFailedException.java
@@ -0,0 +1,28 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 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.runtime;
+
+/**
+ * <code>AssertionFailedException</code> is a runtime exception thrown
+ * by some of the methods in <code>Assert</code>.
+ */
+public class AssertionFailedException extends RuntimeException {
+ /**
+ * All serializable objects should have a stable serialVersionUID
+ */
+ private static final long serialVersionUID = 1L;
+
+ /** Constructs a new exception with the given message.
+ */
+ public AssertionFailedException(String detail) {
+ super(detail);
+ }
+}
diff --git a/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/BundleFinder.java b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/BundleFinder.java
new file mode 100644
index 0000000..810759e
--- /dev/null
+++ b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/BundleFinder.java
@@ -0,0 +1,134 @@
+/*******************************************************************************
+ * Copyright (c) 2005 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.runtime;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Map;
+import org.eclipse.core.internal.runtime.FindSupport;
+import org.osgi.framework.Bundle;
+
+/**
+ * This class contains collection of helper methods aimed at finding files in bundles.
+ * This class can only be used if OSGi plugin is available.
+ *
+ * The class is not intended to be subclassed or instantiated by clients.
+ *
+ * @since org.eclipse.equinox.common 1.0
+ */
+public final class BundleFinder {
+
+ /**
+ * Returns a URL for the given path in the given bundle. Returns <code>null</code> if the URL
+ * could not be computed or created.
+ *
+ * @param bundle the bundle in which to search
+ * @param path path relative to plug-in installation location
+ * @return a URL for the given path or <code>null</code>. The actual form
+ * of the returned URL is not specified.
+ * @see #find(Bundle, IPath, Map)
+ */
+ public static URL find(Bundle bundle, IPath path) {
+ return FindSupport.find(bundle, path, null);
+ }
+
+ /**
+ * Returns a URL for the given path in the given bundle. Returns <code>null</code> if the URL
+ * could not be computed or created.
+ * <p>
+ * find looks for this path in given bundle and any attached fragments.
+ * <code>null</code> is returned if no such entry is found. Note that
+ * there is no specific order to the fragments.
+ * </p><p>
+ * The following arguments may also be used
+ * <pre>
+ * $nl$ - for language specific information
+ * $os$ - for operating system specific information
+ * $ws$ - for windowing system specific information
+ * </pre>
+ * </p><p>
+ * A path of $nl$/about.properties in an environment with a default
+ * locale of en_CA will return a URL corresponding to the first place
+ * about.properties is found according to the following order:
+ * <pre>
+ * plugin root/nl/en/CA/about.properties
+ * fragment1 root/nl/en/CA/about.properties
+ * fragment2 root/nl/en/CA/about.properties
+ * ...
+ * plugin root/nl/en/about.properties
+ * fragment1 root/nl/en/about.properties
+ * fragment2 root/nl/en/about.properties
+ * ...
+ * plugin root/about.properties
+ * fragment1 root/about.properties
+ * fragment2 root/about.properties
+ * ...
+ * </pre>
+ * </p><p>
+ * The current environment variable values can be overridden using
+ * the override map argument.
+ * </p>
+ *
+ * @param bundle the bundle in which to search
+ * @param path file path relative to plug-in installation location
+ * @param override map of override substitution arguments to be used for
+ * any $arg$ path elements. The map keys correspond to the substitution
+ * arguments (eg. "$nl$" or "$os$"). The resulting
+ * values must be of type java.lang.String. If the map is <code>null</code>,
+ * or does not contain the required substitution argument, the default
+ * is used.
+ * @return a URL for the given path or <code>null</code>. The actual form
+ * of the returned URL is not specified.
+ */
+ public static URL find(Bundle bundle, IPath path, Map override) {
+ return FindSupport.find(bundle, path, override);
+ }
+
+ /**
+ * Returns an input stream for the specified file. The file path
+ * must be specified relative to this plug-in's installation location.
+ * Optionally, the platform searches for the correct localized version
+ * of the specified file using the users current locale, and Java
+ * naming convention for localized resource files (locale suffix appended
+ * to the specified file extension).
+ * <p>
+ * The caller must close the returned stream when done.
+ * </p>
+ *
+ * @param bundle the bundle in which to search
+ * @param file path relative to plug-in installation location
+ * @param localized <code>true</code> for the localized version
+ * of the file, and <code>false</code> for the file exactly
+ * as specified
+ * @return an input stream
+ * @exception IOException if the given path cannot be found in this plug-in
+ */
+ public static final InputStream openStream(Bundle bundle, IPath file, boolean localized) throws IOException {
+ return FindSupport.openStream(bundle, file, localized);
+ }
+
+ /**
+ * Returns an input stream for the specified file. The file path
+ * must be specified relative this the plug-in's installation location.
+ *
+ * @param bundle the bundle in which to search
+ * @param file path relative to plug-in installation location
+ * @return an input stream
+ * @exception IOException if the given path cannot be found in this plug-in
+ *
+ * @see #openStream(Bundle,IPath,boolean)
+ */
+ public static final InputStream openStream(Bundle bundle, IPath file) throws IOException {
+ return FindSupport.openStream(bundle, file, false);
+ }
+
+}
diff --git a/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/CoreException.java b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/CoreException.java
new file mode 100644
index 0000000..9ac5b61
--- /dev/null
+++ b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/CoreException.java
@@ -0,0 +1,98 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 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.runtime;
+
+import java.io.PrintStream;
+import java.io.PrintWriter;
+
+/**
+ * A checked exception representing a failure.
+ * <p>
+ * Core exceptions contain a status object describing the
+ * cause of the exception.
+ * </p>
+ *
+ * @see IStatus
+ */
+public class CoreException extends Exception {
+
+ /**
+ * All serializable objects should have a stable serialVersionUID
+ */
+ private static final long serialVersionUID = 1L;
+
+ /** Status object. */
+ private IStatus status;
+
+ /**
+ * Creates a new exception with the given status object. The message
+ * of the given status is used as the exception message.
+ *
+ * @param status the status object to be associated with this exception
+ */
+ public CoreException(IStatus status) {
+ super(status.getMessage());
+ this.status = status;
+ }
+
+ /**
+ * Returns the status object for this exception.
+ *
+ * @return a status object
+ */
+ public final IStatus getStatus() {
+ return status;
+ }
+
+ /**
+ * Prints a stack trace out for the exception, and
+ * any nested exception that it may have embedded in
+ * its Status object.
+ */
+ public void printStackTrace() {
+ printStackTrace(System.err);
+ }
+
+ /**
+ * Prints a stack trace out for the exception, and
+ * any nested exception that it may have embedded in
+ * its Status object.
+ *
+ * @param output the stream to write to
+ */
+ public void printStackTrace(PrintStream output) {
+ synchronized (output) {
+ if (status.getException() != null) {
+ output.print(getClass().getName() + "[" + status.getCode() + "]: "); //$NON-NLS-1$ //$NON-NLS-2$
+ status.getException().printStackTrace(output);
+ } else
+ super.printStackTrace(output);
+ }
+ }
+
+ /**
+ * Prints a stack trace out for the exception, and
+ * any nested exception that it may have embedded in
+ * its Status object.
+ *
+ * @param output the stream to write to
+ */
+ public void printStackTrace(PrintWriter output) {
+ synchronized (output) {
+ if (status.getException() != null) {
+ output.print(getClass().getName() + "[" + status.getCode() + "]: "); //$NON-NLS-1$ //$NON-NLS-2$
+ status.getException().printStackTrace(output);
+ } else
+ super.printStackTrace(output);
+ }
+ }
+
+}
diff --git a/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/IAdaptable.java b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/IAdaptable.java
new file mode 100644
index 0000000..a935fad
--- /dev/null
+++ b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/IAdaptable.java
@@ -0,0 +1,47 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 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.runtime;
+
+/**
+ * An interface for an adaptable object.
+ * <p>
+ * Adaptable objects can be dynamically extended to provide different
+ * interfaces (or "adapters"). Adapters are created by adapter
+ * factories, which are in turn managed by type by adapter managers.
+ * </p>
+ * For example,
+ * <pre>
+ * IAdaptable a = [some adaptable];
+ * IFoo x = (IFoo)a.getAdapter(IFoo.class);
+ * if (x != null)
+ * [do IFoo things with x]
+ * </pre>
+ * <p>
+ * Clients may implement this interface, or obtain a default implementation
+ * of this interface by subclassing <code>PlatformObject</code>.
+ * </p>
+ * @see IAdapterFactory
+ * @see IAdapterManager
+ * @see PlatformObject
+ */
+public interface IAdaptable {
+ /**
+ * Returns an object which is an instance of the given class
+ * associated with this object. Returns <code>null</code> if
+ * no such object can be found.
+ *
+ * @param adapter the adapter class to look up
+ * @return a object castable to the given class,
+ * or <code>null</code> if this object does not
+ * have an adapter for the given class
+ */
+ public Object getAdapter(Class adapter);
+}
diff --git a/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/IAdapterFactory.java b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/IAdapterFactory.java
new file mode 100644
index 0000000..994eaae
--- /dev/null
+++ b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/IAdapterFactory.java
@@ -0,0 +1,53 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 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.runtime;
+
+/**
+ * An adapter factory defines behavioral extensions for
+ * one or more classes that implements the <code>IAdaptable</code>
+ * interface. Adapter factories are registered with an
+ * adapter manager.
+ * <p>
+ * Clients may implement this interface.
+ * </p>
+ *
+ * @see IAdapterManager
+ * @see IAdaptable
+ */
+public interface IAdapterFactory {
+ /**
+ * Returns an object which is an instance of the given class
+ * associated with the given object. Returns <code>null</code> if
+ * no such object can be found.
+ *
+ * @param adaptableObject the adaptable object being queried
+ * (usually an instance of <code>IAdaptable</code>)
+ * @param adapterType the type of adapter to look up
+ * @return a object castable to the given adapter type,
+ * or <code>null</code> if this adapter factory
+ * does not have an adapter of the given type for the
+ * given object
+ */
+ public Object getAdapter(Object adaptableObject, Class adapterType);
+
+ /**
+ * Returns the collection of adapter types handled by this
+ * factory.
+ * <p>
+ * This method is generally used by an adapter manager
+ * to discover which adapter types are supported, in advance
+ * of dispatching any actual <code>getAdapter</code> requests.
+ * </p>
+ *
+ * @return the collection of adapter types
+ */
+ public Class[] getAdapterList();
+}
diff --git a/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/IAdapterManager.java b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/IAdapterManager.java
new file mode 100644
index 0000000..330fe5c
--- /dev/null
+++ b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/IAdapterManager.java
@@ -0,0 +1,222 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 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.runtime;
+
+
+/**
+ * An adapter manager maintains a registry of adapter factories. Clients
+ * directly invoke methods on an adapter manager to register and unregister
+ * adapters. All adaptable objects (that is, objects that implement the <code>IAdaptable</code>
+ * interface) funnel <code>IAdaptable.getAdapter</code> invocations to their
+ * adapter manager's <code>IAdapterManger.getAdapter</code> method. The
+ * adapter manager then forwards this request unmodified to the <code>IAdapterFactory.getAdapter</code>
+ * method on one of the registered adapter factories.
+ * <p>
+ * Adapter factories can be registered programmatically using the <code>registerAdapters</code>
+ * method. Alternatively, they can be registered declaratively using the
+ * <code>org.eclipse.core.runtime.adapters</code> extension point. Factories registered
+ * with this extension point will not be able to provide adapters until their
+ * corresponding plugin has been activated.
+ * <p>
+ * The following code snippet shows how one might register an adapter of type
+ * <code>com.example.acme.Sticky</code> on resources in the workspace.
+ * <p>
+ *
+ * <pre>
+ * IAdapterFactory pr = new IAdapterFactory() {
+ * public Class[] getAdapterList() {
+ * return new Class[] { com.example.acme.Sticky.class };
+ * }
+ * public Object getAdapter(Object adaptableObject, Class adapterType) {
+ * IResource res = (IResource) adaptableObject;
+ * QualifiedName key = new QualifiedName("com.example.acme", "sticky-note");
+ * try {
+ * com.example.acme.Sticky v = (com.example.acme.Sticky) res.getSessionProperty(key);
+ * if (v == null) {
+ * v = new com.example.acme.Sticky();
+ * res.setSessionProperty(key, v);
+ * }
+ * } catch (CoreException e) {
+ * // unable to access session property - ignore
+ * }
+ * return v;
+ * }
+ * }
+ * Platform.getAdapterManager().registerAdapters(pr, IResource.class);
+ * </pre>
+ *
+ * </p>
+ * <p>
+ * This interface is not intended to be implemented by clients.
+ * </p>
+ *
+ * @see IAdaptable
+ * @see IAdapterFactory
+ */
+public interface IAdapterManager {
+
+ /**
+ * Returns the types that can be obtained by converting <code>adaptableClass</code>
+ * via this manager. Converting means that subsequent calls to <code>getAdapter()</code>
+ * or <code>loadAdapter()</code> could result in an adapted object.
+ * <p>
+ * Note that the returned types do not guarantee that
+ * a subsequent call to <code>getAdapter</code> with the same type as an argument
+ * will return a non-null result. If the factory's plug-in has not yet been
+ * loaded, or if the factory itself returns <code>null</code>, then
+ * <code>getAdapter</code> will still return <code>null</code>.
+ * </p>
+ * @param adaptableClass the adaptable class being queried
+ * @return an array of type names that can be obtained by converting
+ * <code>adaptableClass</code> via this manager. An empty array
+ * is returned if there are none.
+ * @since 3.1
+ */
+ public String[] computeAdapterTypes(Class adaptableClass);
+
+ /**
+ * Returns the class search order for a given class. The search order from a
+ * class with the definition <br>
+ * <code>class X extends Y implements A, B</code><br>
+ * is as follows:
+ * <ul>
+ * <li>the target's class: X
+ * <li>X's superclasses in order to <code>Object</code>
+ * <li>a breadth-first traversal of the target class's interfaces in the
+ * order returned by <code>getInterfaces</code> (in the example, A and its
+ * superinterfaces then B and its superinterfaces) </il>
+ * </ul>
+ *
+ * @param clazz the class for which to return the class order.
+ * @return the class search order for the given class. The returned
+ * search order will minimally contain the target class.
+ * @since 3.1
+ */
+ public Class[] computeClassOrder(Class clazz);
+ /**
+ * Returns an object which is an instance of the given class associated
+ * with the given object. Returns <code>null</code> if no such object can
+ * be found.
+ * <p>
+ * Note that this method will never cause plug-ins to be loaded. If the
+ * only suitable factory is not yet loaded, this method will return <code>null</code>.
+ *
+ * @param adaptable the adaptable object being queried (usually an instance
+ * of <code>IAdaptable</code>)
+ * @param adapterType the type of adapter to look up
+ * @return an object castable to the given adapter type, or <code>null</code>
+ * if the given adaptable object does not have an available adapter of the
+ * given type
+ */
+ public Object getAdapter(Object adaptable, Class adapterType);
+
+ /**
+ * Returns an object which is an instance of the given class name associated
+ * with the given object. Returns <code>null</code> if no such object can
+ * be found.
+ * <p>
+ * Note that this method will never cause plug-ins to be loaded. If the
+ * only suitable factory is not yet loaded, this method will return <code>null</code>.
+ * If activation of the plug-in providing the factory is required, use the
+ * <code>loadAdapter</code> method instead.
+ *
+ * @param adaptable the adaptable object being queried (usually an instance
+ * of <code>IAdaptable</code>)
+ * @param adapterTypeName the fully qualified name of the type of adapter to look up
+ * @return an object castable to the given adapter type, or <code>null</code>
+ * if the given adaptable object does not have an available adapter of the
+ * given type
+ * @since 3.0
+ */
+ public Object getAdapter(Object adaptable, String adapterTypeName);
+
+ /**
+ * Returns whether there is an adapter factory registered that may be able
+ * to convert <code>adaptable</code> to an object of type <code>adapterTypeName</code>.
+ * <p>
+ * Note that a return value of <code>true</code> does not guarantee that
+ * a subsequent call to <code>getAdapter</code> with the same arguments
+ * will return a non-null result. If the factory's plug-in has not yet been
+ * loaded, or if the factory itself returns <code>null</code>, then
+ * <code>getAdapter</code> will still return <code>null</code>.
+ *
+ * @param adaptable the adaptable object being queried (usually an instance
+ * of <code>IAdaptable</code>)
+ * @param adapterTypeName the fully qualified class name of an adapter to
+ * look up
+ * @return <code>true</code> if there is an adapter factory that claims
+ * it can convert <code>adaptable</code> to an object of type <code>adapterType</code>,
+ * and <code>false</code> otherwise.
+ * @since 3.0
+ */
+ public boolean hasAdapter(Object adaptable, String adapterTypeName);
+
+ /**
+ * Returns an object that is an instance of the given class name associated
+ * with the given object. Returns <code>null</code> if no such object can
+ * be found.
+ * <p>
+ * Note that unlike the <code>getAdapter</code> methods, this method
+ * will cause the plug-in that contributes the adapter factory to be loaded
+ * if necessary. As such, this method should be used judiciously, in order
+ * to avoid unnecessary plug-in activations. Most clients should avoid
+ * activation by using <code>getAdapter</code> instead.
+ *
+ * @param adaptable the adaptable object being queried (usually an instance
+ * of <code>IAdaptable</code>)
+ * @param adapterTypeName the fully qualified name of the type of adapter to look up
+ * @return an object castable to the given adapter type, or <code>null</code>
+ * if the given adaptable object does not have an available adapter of the
+ * given type
+ * @since 3.0
+ */
+ public Object loadAdapter(Object adaptable, String adapterTypeName);
+
+ /**
+ * Registers the given adapter factory as extending objects of the given
+ * type.
+ * <p>
+ * If the type being extended is a class, the given factory's adapters are
+ * available on instances of that class and any of its subclasses. If it is
+ * an interface, the adapters are available to all classes that directly or
+ * indirectly implement that interface.
+ * </p>
+ *
+ * @param factory the adapter factory
+ * @param adaptable the type being extended
+ * @see #unregisterAdapters(IAdapterFactory)
+ * @see #unregisterAdapters(IAdapterFactory, Class)
+ */
+ public void registerAdapters(IAdapterFactory factory, Class adaptable);
+
+ /**
+ * Removes the given adapter factory completely from the list of registered
+ * factories. Equivalent to calling <code>unregisterAdapters(IAdapterFactory,Class)</code>
+ * on all classes against which it had been explicitly registered. Does
+ * nothing if the given factory is not currently registered.
+ *
+ * @param factory the adapter factory to remove
+ * @see #registerAdapters(IAdapterFactory, Class)
+ */
+ public void unregisterAdapters(IAdapterFactory factory);
+
+ /**
+ * Removes the given adapter factory from the list of factories registered
+ * as extending the given class. Does nothing if the given factory and type
+ * combination is not registered.
+ *
+ * @param factory the adapter factory to remove
+ * @param adaptable one of the types against which the given factory is
+ * registered
+ * @see #registerAdapters(IAdapterFactory, Class)
+ */
+ public void unregisterAdapters(IAdapterFactory factory, Class adaptable);
+}
diff --git a/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/ILogListener.java b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/ILogListener.java
new file mode 100644
index 0000000..b8c4548
--- /dev/null
+++ b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/ILogListener.java
@@ -0,0 +1,33 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 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.runtime;
+
+import java.util.EventListener;
+
+/**
+ * A log listener is notified of entries added to a plug-in's log.
+ * <p>
+ * Clients may implement this interface.
+ * </p>
+ *
+ * @see ILog#addLogListener(ILogListener)
+ * @see Platform#addLogListener(ILogListener)
+ */
+public interface ILogListener extends EventListener {
+ /**
+ * Notifies this listener that given status has been logged by
+ * a plug-in. The listener is free to retain or ignore this status.
+ *
+ * @param status the status being logged
+ * @param plugin the plugin of the log which generated this event
+ */
+ public void logging(IStatus status, String plugin);
+}
diff --git a/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/IPath.java b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/IPath.java
new file mode 100644
index 0000000..d9eb8ca
--- /dev/null
+++ b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/IPath.java
@@ -0,0 +1,504 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 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.runtime;
+
+/**
+ * A path is an ordered collection of string segments,
+ * separated by a standard separator character, "/".
+ * A path may also have a leading and/or a trailing separator.
+ * Paths may also be prefixed by an optional device id, which includes
+ * the character(s) which separate the device id from the rest
+ * of the path. For example, "C:" and "Server/Volume:" are typical
+ * device ids.
+ * A device independent path has <code>null</code> for a device id.
+ * <p>
+ * Note that paths are value objects; all operations on paths
+ * return a new path; the path that is operated on is unscathed.
+ * </p>
+ * <p>
+ * UNC paths are denoted by leading double-slashes such
+ * as <code>//Server/Volume/My/Path</code>. When a new path
+ * is constructed all double-slashes are removed except those
+ * appearing at the beginning of the path.
+ * </p>
+ * <p>
+ * This interface is not intended to be implemented by clients.
+ * </p>
+ *
+ * @see Path
+ */
+public interface IPath extends Cloneable {
+
+ /**
+ * Path separator character constant "/" used in paths.
+ */
+ public static final char SEPARATOR = '/';
+
+ /**
+ * Device separator character constant ":" used in paths.
+ */
+ public static final char DEVICE_SEPARATOR = ':';
+
+ /**
+ * Returns a new path which is the same as this path but with
+ * the given file extension added. If this path is empty, root or has a
+ * trailing separator, this path is returned. If this path already
+ * has an extension, the existing extension is left and the given
+ * extension simply appended. Clients wishing to replace
+ * the current extension should first remove the extension and
+ * then add the desired one.
+ * <p>
+ * The file extension portion is defined as the string
+ * following the last period (".") character in the last segment.
+ * The given extension should not include a leading ".".
+ * </p>
+ *
+ * @param extension the file extension to append
+ * @return the new path
+ */
+ public IPath addFileExtension(String extension);
+
+ /**
+ * Returns a path with the same segments as this path
+ * but with a trailing separator added.
+ * This path must have at least one segment.
+ * <p>
+ * If this path already has a trailing separator,
+ * this path is returned.
+ * </p>
+ *
+ * @return the new path
+ * @see #hasTrailingSeparator()
+ * @see #removeTrailingSeparator()
+ */
+ public IPath addTrailingSeparator();
+
+ /**
+ * Returns the canonicalized path obtained from the
+ * concatenation of the given string path to the
+ * end of this path. The given string path must be a valid
+ * path. If it has a trailing separator,
+ * the result will have a trailing separator.
+ * The device id of this path is preserved (the one
+ * of the given string is ignored). Duplicate slashes
+ * are removed from the path except at the beginning
+ * where the path is considered to be UNC.
+ *
+ * @param path the string path to concatenate
+ * @return the new path
+ * @see #isValidPath(String)
+ */
+ public IPath append(String path);
+
+ /**
+ * Returns the canonicalized path obtained from the
+ * concatenation of the given path's segments to the
+ * end of this path. If the given path has a trailing
+ * separator, the result will have a trailing separator.
+ * The device id of this path is preserved (the one
+ * of the given path is ignored). Duplicate slashes
+ * are removed from the path except at the beginning
+ * where the path is considered to be UNC.
+ *
+ * @param path the path to concatenate
+ * @return the new path
+ */
+ public IPath append(IPath path);
+
+ /**
+ * Returns a copy of this path.
+ *
+ * @return the cloned path
+ */
+ public Object clone();
+
+ /**
+ * Returns whether this path equals the given object.
+ * <p>
+ * Equality for paths is defined to be: same sequence of segments,
+ * same absolute/relative status, and same device.
+ * Trailing separators are disregarded.
+ * Paths are not generally considered equal to objects other than paths.
+ * </p>
+ *
+ * @param obj the other object
+ * @return <code>true</code> if the paths are equivalent,
+ * and <code>false</code> if they are not
+ */
+ public boolean equals(Object obj);
+
+ /**
+ * Returns the device id for this path, or <code>null</code> if this
+ * path has no device id. Note that the result will end in ':'.
+ *
+ * @return the device id, or <code>null</code>
+ * @see #setDevice(String)
+ */
+ public String getDevice();
+
+ /**
+ * Returns the file extension portion of this path,
+ * or <code>null</code> if there is none.
+ * <p>
+ * The file extension portion is defined as the string
+ * following the last period (".") character in the last segment.
+ * If there is no period in the last segment, the path has no
+ * file extension portion. If the last segment ends in a period,
+ * the file extension portion is the empty string.
+ * </p>
+ *
+ * @return the file extension or <code>null</code>
+ */
+ public String getFileExtension();
+
+ /**
+ * Returns whether this path has a trailing separator.
+ * <p>
+ * Note: In the root path ("/"), the separator is considered to
+ * be leading rather than trailing.
+ * </p>
+ *
+ * @return <code>true</code> if this path has a trailing
+ * separator, and <code>false</code> otherwise
+ * @see #addTrailingSeparator()
+ * @see #removeTrailingSeparator()
+ */
+ public boolean hasTrailingSeparator();
+
+ /**
+ * Returns whether this path is an absolute path (ignoring
+ * any device id).
+ * <p>
+ * Absolute paths start with a path separator.
+ * A root path, like <code>/</code> or <code>C:/</code>,
+ * is considered absolute. UNC paths are always absolute.
+ * </p>
+ *
+ * @return <code>true</code> if this path is an absolute path,
+ * and <code>false</code> otherwise
+ */
+ public boolean isAbsolute();
+
+ /**
+ * Returns whether this path has no segments and is not
+ * a root path.
+ *
+ * @return <code>true</code> if this path is empty,
+ * and <code>false</code> otherwise
+ */
+ public boolean isEmpty();
+
+ /**
+ * Returns whether this path is a prefix of the given path.
+ * To be a prefix, this path's segments must
+ * appear in the argument path in the same order,
+ * and their device ids must match.
+ * <p>
+ * An empty path is a prefix of all paths with the same device; a root path is a prefix of
+ * all absolute paths with the same device.
+ * </p>
+ * @param anotherPath the other path
+ * @return <code>true</code> if this path is a prefix of the given path,
+ * and <code>false</code> otherwise
+ */
+ public boolean isPrefixOf(IPath anotherPath);
+
+ /**
+ * Returns whether this path is a root path.
+ * <p>
+ * The root path is the absolute non-UNC path with zero segments;
+ * e.g., <code>/</code> or <code>C:/</code>.
+ * The separator is considered a leading separator, not a trailing one.
+ * </p>
+ *
+ * @return <code>true</code> if this path is a root path,
+ * and <code>false</code> otherwise
+ */
+ public boolean isRoot();
+
+ /**
+ * Returns a boolean value indicating whether or not this path
+ * is considered to be in UNC form. Return false if this path
+ * has a device set or if the first 2 characters of the path string
+ * are not <code>Path.SEPARATOR</code>.
+ *
+ * @return boolean indicating if this path is UNC
+ */
+ public boolean isUNC();
+
+ /**
+ * Returns whether the given string is syntactically correct as
+ * a path. The device id is the prefix up to and including the device
+ * separator for the local file system; the path proper is everything to
+ * the right of it, or the entire string if there is no device separator.
+ * When the platform location is a file system with no meaningful device
+ * separator, the entire string is treated as the path proper.
+ * The device id is not checked for validity; the path proper is correct
+ * if each of the segments in its canonicalized form is valid.
+ *
+ * @param path the path to check
+ * @return <code>true</code> if the given string is a valid path,
+ * and <code>false</code> otherwise
+ * @see #isValidSegment(String)
+ */
+ public boolean isValidPath(String path);
+
+ /**
+ * Returns whether the given string is valid as a segment in
+ * a path. The rules for valid segments are as follows:
+ * <ul>
+ * <li> the empty string is not valid
+ * <li> any string containing the slash character ('/') is not valid
+ * <li>any string containing segment or device separator characters
+ * on the local file system, such as the backslash ('\') and colon (':')
+ * on some file systems.
+ * </ul>
+ *
+ * @param segment the path segment to check
+ * @return <code>true</code> if the given path segment is valid,
+ * and <code>false</code> otherwise
+ */
+ public boolean isValidSegment(String segment);
+
+ /**
+ * Returns the last segment of this path, or
+ * <code>null</code> if it does not have any segments.
+ *
+ * @return the last segment of this path, or <code>null</code>
+ */
+ public String lastSegment();
+
+ /**
+ * Returns an absolute path with the segments and device id of this path.
+ * Absolute paths start with a path separator. If this path is absolute,
+ * it is simply returned.
+ *
+ * @return the new path
+ */
+ public IPath makeAbsolute();
+
+ /**
+ * Returns a relative path with the segments and device id of this path.
+ * Absolute paths start with a path separator and relative paths do not.
+ * If this path is relative, it is simply returned.
+ *
+ * @return the new path
+ */
+ public IPath makeRelative();
+
+ /**
+ * Return a new path which is the equivalent of this path converted to UNC
+ * form (if the given boolean is true) or this path not as a UNC path (if the given
+ * boolean is false). If UNC, the returned path will not have a device and the
+ * first 2 characters of the path string will be <code>Path.SEPARATOR</code>. If not UNC, the
+ * first 2 characters of the returned path string will not be <code>Path.SEPARATOR</code>.
+ *
+ * @param toUNC true if converting to UNC, false otherwise
+ * @return the new path, either in UNC form or not depending on the boolean parameter
+ */
+ public IPath makeUNC(boolean toUNC);
+
+ /**
+ * Returns a count of the number of segments which match in
+ * this path and the given path (device ids are ignored),
+ * comparing in increasing segment number order.
+ *
+ * @param anotherPath the other path
+ * @return the number of matching segments
+ */
+ public int matchingFirstSegments(IPath anotherPath);
+
+ /**
+ * Returns a new path which is the same as this path but with
+ * the file extension removed. If this path does not have an
+ * extension, this path is returned.
+ * <p>
+ * The file extension portion is defined as the string
+ * following the last period (".") character in the last segment.
+ * If there is no period in the last segment, the path has no
+ * file extension portion. If the last segment ends in a period,
+ * the file extension portion is the empty string.
+ * </p>
+ *
+ * @return the new path
+ */
+ public IPath removeFileExtension();
+
+ /**
+ * Returns a copy of this path with the given number of segments
+ * removed from the beginning. The device id is preserved.
+ * The number must be greater or equal zero.
+ * If the count is zero, this path is returned.
+ * The resulting path will always be a relative path with respect
+ * to this path. If the number equals or exceeds the number
+ * of segments in this path, an empty relative path is returned.
+ *
+ * @param count the number of segments to remove
+ * @return the new path
+ */
+ public IPath removeFirstSegments(int count);
+
+ /**
+ * Returns a copy of this path with the given number of segments
+ * removed from the end. The device id is preserved.
+ * The number must be greater or equal zero.
+ * If the count is zero, this path is returned.
+ * <p>
+ * If this path has a trailing separator, it will still
+ * have a trailing separator after the last segments are removed
+ * (assuming there are some segments left). If there is no
+ * trailing separator, the result will not have a trailing
+ * separator.
+ * If the number equals or exceeds the number
+ * of segments in this path, an empty path is returned.
+ * </p>
+ *
+ * @param count the number of segments to remove
+ * @return the new path
+ */
+ public IPath removeLastSegments(int count);
+
+ /**
+ * Returns a path with the same segments as this path
+ * but with a trailing separator removed.
+ * Does nothing if this path does not have at least one segment.
+ * The device id is preserved.
+ * <p>
+ * If this path does not have a trailing separator,
+ * this path is returned.
+ * </p>
+ *
+ * @return the new path
+ * @see #addTrailingSeparator()
+ * @see #hasTrailingSeparator()
+ */
+ public IPath removeTrailingSeparator();
+
+ /**
+ * Returns the specified segment of this path, or
+ * <code>null</code> if the path does not have such a segment.
+ *
+ * @param index the 0-based segment index
+ * @return the specified segment, or <code>null</code>
+ */
+ public String segment(int index);
+
+ /**
+ * Returns the number of segments in this path.
+ * <p>
+ * Note that both root and empty paths have 0 segments.
+ * </p>
+ *
+ * @return the number of segments
+ */
+ public int segmentCount();
+
+ /**
+ * Returns the segments in this path in order.
+ *
+ * @return an array of string segments
+ */
+ public String[] segments();
+
+ /**
+ * Returns a new path which is the same as this path but with
+ * the given device id. The device id must end with a ":".
+ * A device independent path is obtained by passing <code>null</code>.
+ * <p>
+ * For example, "C:" and "Server/Volume:" are typical device ids.
+ * </p>
+ *
+ * @param device the device id or <code>null</code>
+ * @return a new path
+ * @see #getDevice()
+ */
+ public IPath setDevice(String device);
+
+ /**
+ * Returns a <code>java.io.File</code> corresponding to this path.
+ *
+ * @return the file corresponding to this path
+ */
+ public java.io.File toFile();
+
+ /**
+ * Returns a string representation of this path which uses the
+ * platform-dependent path separator defined by <code>java.io.File</code>.
+ * This method is like <code>toString()</code> except that the
+ * latter always uses the same separator (<code>/</code>) regardless of platform.
+ * <p>
+ * This string is suitable for passing to <code>java.io.File(String)</code>.
+ * </p>
+ *
+ * @return a platform-dependent string representation of this path
+ */
+ public String toOSString();
+
+ /**
+ * Returns a platform-neutral string representation of this path. The
+ * format is not specified, except that the resulting string can be
+ * passed back to the <code>Path#fromPortableString(String)</code>
+ * constructor to produce the exact same path on any platform.
+ * <p>
+ * This string is suitable for passing to <code>Path#fromPortableString(String)</code>.
+ * </p>
+ *
+ * @return a platform-neutral string representation of this path
+ * @see Path#fromPortableString(String)
+ * @since 3.1
+ */
+ public String toPortableString();
+
+ /**
+ * Returns a string representation of this path, including its
+ * device id. The same separator, "/", is used on all platforms.
+ * <p>
+ * Example result strings (without and with device id):
+ * <pre>
+ * "/foo/bar.txt"
+ * "bar.txt"
+ * "/foo/"
+ * "foo/"
+ * ""
+ * "/"
+ * "C:/foo/bar.txt"
+ * "C:bar.txt"
+ * "C:/foo/"
+ * "C:foo/"
+ * "C:"
+ * "C:/"
+ * </pre>
+ * This string is suitable for passing to <code>Path(String)</code>.
+ * </p>
+ *
+ * @return a string representation of this path
+ * @see Path
+ */
+ public String toString();
+
+ /**
+ * Returns a copy of this path truncated after the
+ * given number of segments. The number must not be negative.
+ * The device id is preserved.
+ * <p>
+ * If this path has a trailing separator, the result will too
+ * (assuming there are some segments left). If there is no
+ * trailing separator, the result will not have a trailing
+ * separator.
+ * Copying up to segment zero simply means making an copy with
+ * no path segments.
+ * </p>
+ *
+ * @param count the segment number at which to truncate the path
+ * @return the new path
+ */
+ public IPath uptoSegment(int count);
+}
diff --git a/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/IProgressMonitor.java b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/IProgressMonitor.java
new file mode 100644
index 0000000..3a8f2d7
--- /dev/null
+++ b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/IProgressMonitor.java
@@ -0,0 +1,128 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 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.runtime;
+
+/**
+ * The <code>IProgressMonitor</code> interface is implemented
+ * by objects that monitor the progress of an activity; the methods
+ * in this interface are invoked by code that performs the activity.
+ * <p>
+ * All activity is broken down into a linear sequence of tasks against
+ * which progress is reported. When a task begins, a <code>beginTask(String, int)
+ * </code> notification is reported, followed by any number and mixture of
+ * progress reports (<code>worked()</code>) and subtask notifications
+ * (<code>subTask(String)</code>). When the task is eventually completed, a
+ * <code>done()</code> notification is reported. After the <code>done()</code>
+ * notification, the progress monitor cannot be reused; i.e., <code>
+ * beginTask(String, int)</code> cannot be called again after the call to
+ * <code>done()</code>.
+ * </p>
+ * <p>
+ * A request to cancel an operation can be signaled using the
+ * <code>setCanceled</code> method. Operations taking a progress
+ * monitor are expected to poll the monitor (using <code>isCanceled</code>)
+ * periodically and abort at their earliest convenience. Operation can however
+ * choose to ignore cancelation requests.
+ * </p>
+ * <p>
+ * Since notification is synchronous with the activity itself, the listener should
+ * provide a fast and robust implementation. If the handling of notifications would
+ * involve blocking operations, or operations which might throw uncaught exceptions,
+ * the notifications should be queued, and the actual processing deferred (or perhaps
+ * delegated to a separate thread).
+ * </p>
+ * <p>
+ * Clients may implement this interface.
+ * </p>
+ */
+public interface IProgressMonitor {
+
+ /** Constant indicating an unknown amount of work.
+ */
+ public final static int UNKNOWN = -1;
+
+ /**
+ * Notifies that the main task is beginning. This must only be called once
+ * on a given progress monitor instance.
+ *
+ * @param name the name (or description) of the main task
+ * @param totalWork the total number of work units into which
+ * the main task is been subdivided. If the value is <code>UNKNOWN</code>
+ * the implementation is free to indicate progress in a way which
+ * doesn't require the total number of work units in advance.
+ */
+ public void beginTask(String name, int totalWork);
+
+ /**
+ * Notifies that the work is done; that is, either the main task is completed
+ * or the user canceled it. This method may be called more than once
+ * (implementations should be prepared to handle this case).
+ */
+ public void done();
+
+ /**
+ * Internal method to handle scaling correctly. This method
+ * must not be called by a client. Clients should
+ * always use the method </code>worked(int)</code>.
+ *
+ * @param work the amount of work done
+ */
+ public void internalWorked(double work);
+
+ /**
+ * Returns whether cancelation of current operation has been requested.
+ * Long-running operations should poll to see if cancelation
+ * has been requested.
+ *
+ * @return <code>true</code> if cancellation has been requested,
+ * and <code>false</code> otherwise
+ * @see #setCanceled(boolean)
+ */
+ public boolean isCanceled();
+
+ /**
+ * Sets the cancel state to the given value.
+ *
+ * @param value <code>true</code> indicates that cancelation has
+ * been requested (but not necessarily acknowledged);
+ * <code>false</code> clears this flag
+ * @see #isCanceled()
+ */
+ public void setCanceled(boolean value);
+
+ /**
+ * Sets the task name to the given value. This method is used to
+ * restore the task label after a nested operation was executed.
+ * Normally there is no need for clients to call this method.
+ *
+ * @param name the name (or description) of the main task
+ * @see #beginTask(java.lang.String, int)
+ */
+ public void setTaskName(String name);
+
+ /**
+ * Notifies that a subtask of the main task is beginning.
+ * Subtasks are optional; the main task might not have subtasks.
+ *
+ * @param name the name (or description) of the subtask
+ */
+ public void subTask(String name);
+
+ /**
+ * Notifies that a given number of work unit of the main task
+ * has been completed. Note that this amount represents an
+ * installment, as opposed to a cumulative amount of work done
+ * to date.
+ *
+ * @param work the number of work units just completed
+ */
+ public void worked(int work);
+}
diff --git a/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/IProgressMonitorWithBlocking.java b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/IProgressMonitorWithBlocking.java
new file mode 100644
index 0000000..23a2bac
--- /dev/null
+++ b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/IProgressMonitorWithBlocking.java
@@ -0,0 +1,60 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 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 - Initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.runtime;
+
+/**
+ * An extension to the IProgressMonitor interface for monitors that want to
+ * support feedback when an activity is blocked due to concurrent activity in
+ * another thread.
+ * <p>
+ * When a monitor that supports this extension is passed to an operation, the
+ * operation should call <code>setBlocked</code> whenever it knows that it
+ * must wait for a lock that is currently held by another thread. The operation
+ * should continue to check for and respond to cancelation requests while
+ * blocked. When the operation is no longer blocked, it must call <code>clearBlocked</code>
+ * to clear the blocked state.
+ * <p>
+ * Clients may implement this interface.
+ * </p>
+ * @see IProgressMonitor
+ * @since 3.0
+ */
+public interface IProgressMonitorWithBlocking extends IProgressMonitor {
+ /**
+ * Indicates that this operation is blocked by some background activity. If
+ * a running operation ever calls <code>setBlocked</code>, it must
+ * eventually call <code>clearBlocked</code> before the operation
+ * completes.
+ * <p>
+ * If the caller is blocked by a currently executing job, this method will return
+ * an <code>IJobStatus</code> indicating the job that is currently blocking
+ * the caller. If this blocking job is not known, this method will return a plain
+ * informational <code>IStatus</code> object.
+ * </p>
+ *
+ * @param reason an optional status object whose message describes the
+ * reason why this operation is blocked, or <code>null</code> if this
+ * information is not available.
+ * @see #clearBlocked()
+ * @see org.eclipse.core.runtime.jobs.IJobStatus
+ */
+ public void setBlocked(IStatus reason);
+
+ /**
+ * Clears the blocked state of the running operation. If a running
+ * operation ever calls <code>setBlocked</code>, it must eventually call
+ * <code>clearBlocked</code> before the operation completes.
+ *
+ * @see #setBlocked(IStatus)
+ */
+ public void clearBlocked();
+
+}
diff --git a/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/ISafeRunnable.java b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/ISafeRunnable.java
new file mode 100644
index 0000000..02a7ce1
--- /dev/null
+++ b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/ISafeRunnable.java
@@ -0,0 +1,48 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 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.runtime;
+
+/**
+ * Safe runnables represent blocks of code and associated exception
+ * handlers. They are typically used when a plug-in needs to call some
+ * untrusted code (e.g., code contributed by another plug-in via an
+ * extension).
+ * <p>
+ * Clients may implement this interface.
+ * </p>
+ *
+ * @see Platform#run(ISafeRunnable)
+ */
+public interface ISafeRunnable {
+ /**
+ * Handles an exception thrown by this runnable's <code>run</code>
+ * method. The processing done here should be specific to the
+ * particular usecase for this runnable. Generalized exception
+ * processing (e.g., logging in the platform's log) is done by the
+ * Platform's run mechanism.
+ *
+ * @param exception an exception which occurred during processing
+ * the body of this runnable (i.e., in <code>run()</code>)
+ * @see Platform#run(ISafeRunnable)
+ */
+ public void handleException(Throwable exception);
+
+ /**
+ * Runs this runnable. Any exceptions thrown from this method will
+ * be passed to this runnable's <code>handleException</code>
+ * method.
+ *
+ * @exception Exception if a problem occurred while running this method.
+ * The exception will be processed by <code>handleException</code>
+ * @see Platform#run(ISafeRunnable)
+ */
+ public void run() throws Exception;
+}
diff --git a/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/IStatus.java b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/IStatus.java
new file mode 100644
index 0000000..a241885
--- /dev/null
+++ b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/IStatus.java
@@ -0,0 +1,184 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 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.runtime;
+
+/**
+ * A status object represents the outcome of an operation.
+ * All <code>CoreException</code>s carry a status object to indicate
+ * what went wrong. Status objects are also returned by methods needing
+ * to provide details of failures (e.g., validation methods).
+ * <p>
+ * A status carries the following information:
+ * <ul>
+ * <li> plug-in identifier (required)</li>
+ * <li> severity (required)</li>
+ * <li> status code (required)</li>
+ * <li> message (required) - localized to current locale</li>
+ * <li> exception (optional) - for problems stemming from a failure at
+ * a lower level</li>
+ * </ul>
+ * Some status objects, known as multi-statuses, have other status objects
+ * as children.
+ * </p>
+ * <p>
+ * The class <code>Status</code> is the standard public implementation
+ * of status objects; the subclass <code>MultiStatus</code> is the
+ * implements multi-status objects.
+ * </p>
+ * @see MultiStatus
+ * @see Status
+ */
+public interface IStatus {
+
+ /** Status severity constant (value 0) indicating this status represents the nominal case.
+ * This constant is also used as the status code representing the nominal case.
+ * @see #getSeverity()
+ * @see #isOK()
+ */
+ public static final int OK = 0;
+
+ /** Status type severity (bit mask, value 1) indicating this status is informational only.
+ * @see #getSeverity()
+ * @see #matches(int)
+ */
+ public static final int INFO = 0x01;
+
+ /** Status type severity (bit mask, value 2) indicating this status represents a warning.
+ * @see #getSeverity()
+ * @see #matches(int)
+ */
+ public static final int WARNING = 0x02;
+
+ /** Status type severity (bit mask, value 4) indicating this status represents an error.
+ * @see #getSeverity()
+ * @see #matches(int)
+ */
+ public static final int ERROR = 0x04;
+
+ /** Status type severity (bit mask, value 8) indicating this status represents a
+ * cancelation
+ * @see #getSeverity()
+ * @see #matches(int)
+ * @since 3.0
+ */
+ public static final int CANCEL = 0x08;
+
+ /**
+ * Returns a list of status object immediately contained in this
+ * multi-status, or an empty list if this is not a multi-status.
+ *
+ * @return an array of status objects
+ * @see #isMultiStatus()
+ */
+ public IStatus[] getChildren();
+
+ /**
+ * Returns the plug-in-specific status code describing the outcome.
+ *
+ * @return plug-in-specific status code
+ */
+ public int getCode();
+
+ /**
+ * Returns the relevant low-level exception, or <code>null</code> if none.
+ * For example, when an operation fails because of a network communications
+ * failure, this might return the <code>java.io.IOException</code>
+ * describing the exact nature of that failure.
+ *
+ * @return the relevant low-level exception, or <code>null</code> if none
+ */
+ public Throwable getException();
+
+ /**
+ * Returns the message describing the outcome.
+ * The message is localized to the current locale.
+ *
+ * @return a localized message
+ */
+ public String getMessage();
+
+ /**
+ * Returns the unique identifier of the plug-in associated with this status
+ * (this is the plug-in that defines the meaning of the status code).
+ *
+ * @return the unique identifier of the relevant plug-in
+ */
+ public String getPlugin();
+
+ /**
+ * Returns the severity. The severities are as follows (in
+ * descending order):
+ * <ul>
+ * <li><code>CANCEL</code> - cancelation occurred</li>
+ * <li><code>ERROR</code> - a serious error (most severe)</li>
+ * <li><code>WARNING</code> - a warning (less severe)</li>
+ * <li><code>INFO</code> - an informational ("fyi") message (least severe)</li>
+ * <li><code>OK</code> - everything is just fine</li>
+ * </ul>
+ * <p>
+ * The severity of a multi-status is defined to be the maximum
+ * severity of any of its children, or <code>OK</code> if it has
+ * no children.
+ * </p>
+ *
+ * @return the severity: one of <code>OK</code>, <code>ERROR</code>,
+ * <code>INFO</code>, <code>WARNING</code>, or <code>CANCEL</code>
+ * @see #matches(int)
+ */
+ public int getSeverity();
+
+ /**
+ * Returns whether this status is a multi-status.
+ * A multi-status describes the outcome of an operation
+ * involving multiple operands.
+ * <p>
+ * The severity of a multi-status is derived from the severities
+ * of its children; a multi-status with no children is
+ * <code>OK</code> by definition.
+ * A multi-status carries a plug-in identifier, a status code,
+ * a message, and an optional exception. Clients may treat
+ * multi-status objects in a multi-status unaware way.
+ * </p>
+ *
+ * @return <code>true</code> for a multi-status,
+ * <code>false</code> otherwise
+ * @see #getChildren()
+ */
+ public boolean isMultiStatus();
+
+ /**
+ * Returns whether this status indicates everything is okay
+ * (neither info, warning, nor error).
+ *
+ * @return <code>true</code> if this status has severity
+ * <code>OK</code>, and <code>false</code> otherwise
+ */
+ public boolean isOK();
+
+ /**
+ * Returns whether the severity of this status matches the given
+ * severity mask. Note that a status with severity <code>OK</code>
+ * will never match; use <code>isOK</code> instead to detect
+ * a status with a severity of <code>OK</code>.
+ *
+ * @param severityMask a mask formed by bitwise or'ing severity mask
+ * constants (<code>ERROR</code>, <code>WARNING</code>,
+ * <code>INFO</code>, <code>CANCEL</code>)
+ * @return <code>true</code> if there is at least one match,
+ * <code>false</code> if there are no matches
+ * @see #getSeverity()
+ * @see #CANCEL
+ * @see #ERROR
+ * @see #WARNING
+ * @see #INFO
+ */
+ public boolean matches(int severityMask);
+}
diff --git a/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/ListenerList.java b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/ListenerList.java
new file mode 100644
index 0000000..f5c833a
--- /dev/null
+++ b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/ListenerList.java
@@ -0,0 +1,169 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2005 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.runtime;
+
+/**
+ * This class is a thread safe list that is designed for storing lists of listeners.
+ * The implementation is optimized for minimal memory footprint, frequent reads
+ * and infrequent writes. Modification of the list is synchronized and relatively
+ * expensive, while accessing the listeners is very fast. Readers are given access
+ * to the underlying array data structure for reading, with the trust that they will
+ * not modify the underlying array.
+ * <p>
+ * <a name="same">A listener list handles the <i>same</i> listener being added
+ * multiple times, and tolerates removal of listeners that are the same as other
+ * listeners in the list. For this purpose, listeners can be compared with each other
+ * using either equality or identity, as specified in the list constructor.
+ * </p>
+ *
+ * @since org.eclipse.equinox.common 1.0
+ */
+public class ListenerList {
+
+ /**
+ * The empty array singleton instance.
+ */
+ private static final Object[] EmptyArray = new Object[0];
+
+ /**
+ * Mode constant (value 0) indicating that listeners should be considered
+ * the <a href="#same">same</a> if they are equal.
+ */
+ public static final int EQUALITY = 0;
+
+ /**
+ * Mode constant (value 1) indicating that listeners should be considered
+ * the <a href="#same">same</a> if they are identical.
+ */
+ public static final int IDENTITY = 1;
+
+ /**
+ * Indicates the comparison mode used to determine if two
+ * listeners are equivalent
+ */
+ private final boolean identity;
+
+ /**
+ * The list of listeners. Initially empty but initialized
+ * to an array of size capacity the first time a listener is added.
+ * Maintains invariant: listeners != null
+ */
+ private volatile Object[] listeners = EmptyArray;
+
+ /**
+ * Creates a listener list in which listeners are compared using equality.
+ */
+ public ListenerList() {
+ this(EQUALITY);
+ }
+
+ /**
+ * Creates a listener list using the provided comparison mode.
+ *
+ * @param mode The mode used to determine if listeners are the <a href="#same">same</a>.
+ */
+ public ListenerList(int mode) {
+ if (mode != EQUALITY && mode != IDENTITY)
+ throw new IllegalArgumentException();
+ this.identity = mode == IDENTITY;
+ }
+
+ /**
+ * Adds a listener to this list. This method has no effect if the <a href="#same">same</a>
+ * listener is already registered.
+ *
+ * @param listener the listener to add
+ */
+ public synchronized void add(Object listener) {
+ // This method is synchronized to protect against multiple threads adding
+ // or removing listeners concurrently. This does not block concurrent readers.
+ if (listener == null)
+ throw new IllegalArgumentException();
+ // check for duplicates
+ final int oldSize = listeners.length;
+ for (int i = 0; i < oldSize; ++i) {
+ Object listener2 = listeners[i];
+ if (identity ? listener == listener2 : listener.equals(listener2))
+ return;
+ }
+ // Thread safety: create new array to avoid affecting concurrent readers
+ Object[] newListeners = new Object[oldSize + 1];
+ System.arraycopy(listeners, 0, newListeners, 0, oldSize);
+ newListeners[oldSize] = listener;
+ //atomic assignment
+ this.listeners = newListeners;
+ }
+
+ /**
+ * Returns an array containing all the registered listeners.
+ * The resulting array is unaffected by subsequent adds or removes.
+ * If there are no listeners registered, the result is an empty array.
+ * Use this method when notifying listeners, so that any modifications
+ * to the listener list during the notification will have no effect on
+ * the notification itself.
+ * <p>
+ * Note: Callers of this method <b>must not</b> modify the returned array.
+ *
+ * @return the list of registered listeners
+ */
+ public Object[] getListeners() {
+ return listeners;
+ }
+
+ /**
+ * Returns whether this listener list is empty.
+ *
+ * @return <code>true</code> if there are no registered listeners, and
+ * <code>false</code> otherwise
+ */
+ public boolean isEmpty() {
+ return listeners.length == 0;
+ }
+
+ /**
+ * Removes a listener from this list. Has no effect if the <a href="#same">same</a>
+ * listener was not already registered.
+ *
+ * @param listener the listener to remove
+ */
+ public synchronized void remove(Object listener) {
+ // This method is synchronized to protect against multiple threads adding
+ // or removing listeners concurrently. This does not block concurrent readers.
+ if (listener == null)
+ throw new IllegalArgumentException();
+ int oldSize = listeners.length;
+ for (int i = 0; i < oldSize; ++i) {
+ Object listener2 = listeners[i];
+ if (identity ? listener == listener2 : listener.equals(listener2)) {
+ if (oldSize == 1) {
+ listeners = EmptyArray;
+ } else {
+ // Thread safety: create new array to avoid affecting concurrent readers
+ Object[] newListeners = new Object[oldSize - 1];
+ System.arraycopy(listeners, 0, newListeners, 0, i);
+ System.arraycopy(listeners, i + 1, newListeners, i, oldSize - i - 1);
+ //atomic assignment to field
+ this.listeners = newListeners;
+ }
+ return;
+ }
+ }
+ }
+
+ /**
+ * Returns the number of registered listeners.
+ *
+ * @return the number of registered listeners
+ */
+ public int size() {
+ return listeners.length;
+ }
+}
diff --git a/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/MultiStatus.java b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/MultiStatus.java
new file mode 100644
index 0000000..5d8ed50
--- /dev/null
+++ b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/MultiStatus.java
@@ -0,0 +1,146 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 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.runtime;
+
+/**
+ * A concrete multi-status implementation,
+ * suitable either for instantiating or subclassing.
+ */
+public class MultiStatus extends Status {
+
+ /** List of child statuses.
+ */
+ private IStatus[] children;
+
+ /**
+ * Creates and returns a new multi-status object with the given children.
+ *
+ * @param pluginId the unique identifier of the relevant plug-in
+ * @param code the plug-in-specific status code
+ * @param newChildren the list of children status objects
+ * @param message a human-readable message, localized to the
+ * current locale
+ * @param exception a low-level exception, or <code>null</code> if not
+ * applicable
+ */
+ public MultiStatus(String pluginId, int code, IStatus[] newChildren, String message, Throwable exception) {
+ this(pluginId, code, message, exception);
+ Assert.isLegal(newChildren != null);
+ int maxSeverity = getSeverity();
+ for (int i = 0; i < newChildren.length; i++) {
+ Assert.isLegal(newChildren[i] != null);
+ int severity = newChildren[i].getSeverity();
+ if (severity > maxSeverity)
+ maxSeverity = severity;
+ }
+ this.children = new IStatus[newChildren.length];
+ setSeverity(maxSeverity);
+ System.arraycopy(newChildren, 0, this.children, 0, newChildren.length);
+ }
+
+ /**
+ * Creates and returns a new multi-status object with no children.
+ *
+ * @param pluginId the unique identifier of the relevant plug-in
+ * @param code the plug-in-specific status code
+ * @param message a human-readable message, localized to the
+ * current locale
+ * @param exception a low-level exception, or <code>null</code> if not
+ * applicable
+ */
+ public MultiStatus(String pluginId, int code, String message, Throwable exception) {
+ super(OK, pluginId, code, message, exception);
+ children = new IStatus[0];
+ }
+
+ /**
+ * Adds the given status to this multi-status.
+ *
+ * @param status the new child status
+ */
+ public void add(IStatus status) {
+ Assert.isLegal(status != null);
+ IStatus[] result = new IStatus[children.length + 1];
+ System.arraycopy(children, 0, result, 0, children.length);
+ result[result.length - 1] = status;
+ children = result;
+ int newSev = status.getSeverity();
+ if (newSev > getSeverity()) {
+ setSeverity(newSev);
+ }
+ }
+
+ /**
+ * Adds all of the children of the given status to this multi-status.
+ * Does nothing if the given status has no children (which includes
+ * the case where it is not a multi-status).
+ *
+ * @param status the status whose children are to be added to this one
+ */
+ public void addAll(IStatus status) {
+ Assert.isLegal(status != null);
+ IStatus[] statuses = status.getChildren();
+ for (int i = 0; i < statuses.length; i++) {
+ add(statuses[i]);
+ }
+ }
+
+ /* (Intentionally not javadoc'd)
+ * Implements the corresponding method on <code>IStatus</code>.
+ */
+ public IStatus[] getChildren() {
+ return children;
+ }
+
+ /* (Intentionally not javadoc'd)
+ * Implements the corresponding method on <code>IStatus</code>.
+ */
+ public boolean isMultiStatus() {
+ return true;
+ }
+
+ /**
+ * Merges the given status into this multi-status.
+ * Equivalent to <code>add(status)</code> if the
+ * given status is not a multi-status.
+ * Equivalent to <code>addAll(status)</code> if the
+ * given status is a multi-status.
+ *
+ * @param status the status to merge into this one
+ * @see #add(IStatus)
+ * @see #addAll(IStatus)
+ */
+ public void merge(IStatus status) {
+ Assert.isLegal(status != null);
+ if (!status.isMultiStatus()) {
+ add(status);
+ } else {
+ addAll(status);
+ }
+ }
+
+ /**
+ * Returns a string representation of the status, suitable
+ * for debugging purposes only.
+ */
+ public String toString() {
+ StringBuffer buf = new StringBuffer(super.toString());
+ buf.append(" children=["); //$NON-NLS-1$
+ for (int i = 0; i < children.length; i++) {
+ if (i != 0) {
+ buf.append(" "); //$NON-NLS-1$
+ }
+ buf.append(children[i].toString());
+ }
+ buf.append("]"); //$NON-NLS-1$
+ return buf.toString();
+ }
+}
diff --git a/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/NullProgressMonitor.java b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/NullProgressMonitor.java
new file mode 100644
index 0000000..6c54d47
--- /dev/null
+++ b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/NullProgressMonitor.java
@@ -0,0 +1,124 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 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.runtime;
+
+/**
+ * A default progress monitor implementation suitable for
+ * subclassing.
+ * <p>
+ * This implementation supports cancelation. The default
+ * implementations of the other methods do nothing.
+ * </p>
+ */
+public class NullProgressMonitor implements IProgressMonitor {
+
+ /**
+ * Indicates whether cancel has been requested.
+ */
+ private boolean cancelled = false;
+
+ /**
+ * Constructs a new progress monitor.
+ */
+ public NullProgressMonitor() {
+ super();
+ }
+
+ /**
+ * This implementation does nothing.
+ * Subclasses may override this method to do interesting
+ * processing when a task begins.
+ *
+ * @see IProgressMonitor#beginTask(String, int)
+ */
+ public void beginTask(String name, int totalWork) {
+ // do nothing
+ }
+
+ /**
+ * This implementation does nothing.
+ * Subclasses may override this method to do interesting
+ * processing when a task is done.
+ *
+ * @see IProgressMonitor#done()
+ */
+ public void done() {
+ // do nothing
+ }
+
+ /**
+ * This implementation does nothing.
+ * Subclasses may override this method.
+ *
+ * @see IProgressMonitor#internalWorked(double)
+ */
+ public void internalWorked(double work) {
+ // do nothing
+ }
+
+ /**
+ * This implementation returns the value of the internal
+ * state variable set by <code>setCanceled</code>.
+ * Subclasses which override this method should
+ * override <code>setCanceled</code> as well.
+ *
+ * @see IProgressMonitor#isCanceled()
+ * @see IProgressMonitor#setCanceled(boolean)
+ */
+ public boolean isCanceled() {
+ return cancelled;
+ }
+
+ /**
+ * This implementation sets the value of an internal state variable.
+ * Subclasses which override this method should override
+ * <code>isCanceled</code> as well.
+ *
+ * @see IProgressMonitor#isCanceled()
+ * @see IProgressMonitor#setCanceled(boolean)
+ */
+ public void setCanceled(boolean cancelled) {
+ this.cancelled = cancelled;
+ }
+
+ /**
+ * This implementation does nothing.
+ * Subclasses may override this method to do something
+ * with the name of the task.
+ *
+ * @see IProgressMonitor#setTaskName(String)
+ */
+ public void setTaskName(String name) {
+ // do nothing
+ }
+
+ /**
+ * This implementation does nothing.
+ * Subclasses may override this method to do interesting
+ * processing when a subtask begins.
+ *
+ * @see IProgressMonitor#subTask(String)
+ */
+ public void subTask(String name) {
+ // do nothing
+ }
+
+ /**
+ * This implementation does nothing.
+ * Subclasses may override this method to do interesting
+ * processing when some work has been completed.
+ *
+ * @see IProgressMonitor#worked(int)
+ */
+ public void worked(int work) {
+ // do nothing
+ }
+}
diff --git a/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/OperationCanceledException.java b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/OperationCanceledException.java
new file mode 100644
index 0000000..c013735
--- /dev/null
+++ b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/OperationCanceledException.java
@@ -0,0 +1,42 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2005 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.runtime;
+
+/**
+ * This exception is thrown to blow out of a long-running method
+ * when the user cancels it.
+ * <p>
+ * This class is not intended to be subclassed by clients but
+ * may be instantiated.
+ * </p>
+ */
+public final class OperationCanceledException extends RuntimeException {
+ /**
+ * All serializable objects should have a stable serialVersionUID
+ */
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Creates a new exception.
+ */
+ public OperationCanceledException() {
+ super();
+ }
+
+ /**
+ * Creates a new exception with the given message.
+ *
+ * @param message the message for the exception
+ */
+ public OperationCanceledException(String message) {
+ super(message);
+ }
+}
diff --git a/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/Path.java b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/Path.java
new file mode 100644
index 0000000..6eb25ca
--- /dev/null
+++ b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/Path.java
@@ -0,0 +1,986 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2005 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.runtime;
+
+import java.io.File;
+
+/**
+ * The standard implementation of the <code>IPath</code> interface.
+ * Paths are always maintained in canonicalized form. That is, parent
+ * references (i.e., <code>../../</code>) and duplicate separators are
+ * resolved. For example,
+ * <pre> new Path("/a/b").append("../foo/bar")</pre>
+ * will yield the path
+ * <pre> /a/foo/bar</pre>
+ * <p>
+ * This class is not intended to be subclassed by clients but
+ * may be instantiated.
+ * </p>
+ * @see IPath
+ */
+public class Path implements IPath, Cloneable {
+ /** masks for separator values */
+ private static final int HAS_LEADING = 1;
+ private static final int IS_UNC = 2;
+ private static final int HAS_TRAILING = 4;
+
+ private static final int ALL_SEPARATORS = HAS_LEADING | IS_UNC | HAS_TRAILING;
+
+ /** Constant empty string value. */
+ private static final String EMPTY_STRING = ""; //$NON-NLS-1$
+
+ /** Constant value indicating no segments */
+ private static final String[] NO_SEGMENTS = new String[0];
+
+ /** Constant value containing the empty path with no device. */
+ public static final Path EMPTY = new Path(EMPTY_STRING);
+
+ /** Mask for all bits that are involved in the hash code */
+ private static final int HASH_MASK = ~HAS_TRAILING;
+
+
+ /** Constant root path string (<code>"/"</code>). */
+ private static final String ROOT_STRING = "/"; //$NON-NLS-1$
+
+ /** Constant value containing the root path with no device. */
+ public static final Path ROOT = new Path(ROOT_STRING);
+
+ /** Constant value indicating if the current platform is Windows */
+ private static final boolean WINDOWS = java.io.File.separatorChar == '\\';
+
+ /** The device id string. May be null if there is no device. */
+ private String device = null;
+
+ //Private implementation note: the segments and separators
+ //arrays are never modified, so that they can be shared between
+ //path instances
+
+ /** The path segments */
+ private String[] segments;
+
+ /** flags indicating separators (has leading, is UNC, has trailing) */
+ private int separators;
+
+ /**
+ * Constructs a new path from the given string path.
+ * The string path must represent a valid file system path
+ * on the local file system.
+ * The path is canonicalized and double slashes are removed
+ * except at the beginning. (to handle UNC paths). All forward
+ * slashes ('/') are treated as segment delimiters, and any
+ * segment and device delimiters for the local file system are
+ * also respected.
+ *
+ * @param pathString the portable string path
+ * @see IPath#toPortableString()
+ * @since 3.1
+ */
+ public static IPath fromOSString(String pathString) {
+ return new Path(pathString);
+ }
+
+ /**
+ * Constructs a new path from the given path string.
+ * The path string must have been produced by a previous
+ * call to <code>IPath.toPortableString</code>.
+ *
+ * @param pathString the portable path string
+ * @see IPath#toPortableString()
+ * @since 3.1
+ */
+ public static IPath fromPortableString(String pathString) {
+ int firstMatch = pathString.indexOf(DEVICE_SEPARATOR) +1;
+ //no extra work required if no device characters
+ if (firstMatch <= 0)
+ return new Path().initialize(null, pathString);
+ //if we find a single colon, then the path has a device
+ String devicePart = null;
+ int pathLength = pathString.length();
+ if (firstMatch == pathLength || pathString.charAt(firstMatch) != DEVICE_SEPARATOR) {
+ devicePart = pathString.substring(0, firstMatch);
+ pathString = pathString.substring(firstMatch, pathLength);
+ }
+ //optimize for no colon literals
+ if (pathString.indexOf(DEVICE_SEPARATOR) == -1)
+ return new Path().initialize(devicePart, pathString);
+ //contract colon literals
+ char[] chars = pathString.toCharArray();
+ int readOffset = 0, writeOffset = 0, length = chars.length;
+ while (readOffset < length) {
+ if (chars[readOffset] == DEVICE_SEPARATOR)
+ if (++readOffset >= length)
+ break;
+ chars[writeOffset++] = chars[readOffset++];
+ }
+ return new Path().initialize(devicePart, new String(chars, 0, writeOffset));
+ }
+
+ /* (Intentionally not included in javadoc)
+ * Private constructor.
+ */
+ private Path() {
+ // not allowed
+ }
+
+ /**
+ * Constructs a new path from the given string path.
+ * The string path must represent a valid file system path
+ * on the local file system.
+ * The path is canonicalized and double slashes are removed
+ * except at the beginning. (to handle UNC paths). All forward
+ * slashes ('/') are treated as segment delimiters, and any
+ * segment and device delimiters for the local file system are
+ * also respected (such as colon (':') and backslash ('\') on some file systems).
+ *
+ * @param fullPath the string path
+ * @see #isValidPath(String)
+ */
+ public Path(String fullPath) {
+ String devicePart = null;
+ if (WINDOWS) {
+ //convert backslash to forward slash
+ fullPath = fullPath.indexOf('\\') == -1 ? fullPath : fullPath.replace('\\', SEPARATOR);
+ //extract device
+ int i = fullPath.indexOf(DEVICE_SEPARATOR);
+ if (i != -1) {
+ //remove leading slash from device part to handle output of URL.getFile()
+ int start = fullPath.charAt(0) == SEPARATOR ? 1 : 0;
+ devicePart = fullPath.substring(start, i + 1);
+ fullPath = fullPath.substring(i + 1, fullPath.length());
+ }
+ }
+ initialize(devicePart, fullPath);
+ }
+
+ /**
+ * Constructs a new path from the given device id and string path.
+ * The given string path must be valid.
+ * The path is canonicalized and double slashes are removed except
+ * at the beginning (to handle UNC paths). All forward
+ * slashes ('/') are treated as segment delimiters, and any
+ * segment delimiters for the local file system are
+ * also respected (such as backslash ('\') on some file systems).
+ *
+ * @param device the device id
+ * @param path the string path
+ * @see #isValidPath(String)
+ * @see #setDevice(String)
+ */
+ public Path(String device, String path) {
+ if (WINDOWS) {
+ //convert backslash to forward slash
+ path = path.indexOf('\\') == -1 ? path : path.replace('\\', SEPARATOR);
+ }
+ initialize(device, path);
+ }
+
+ /* (Intentionally not included in javadoc)
+ * Private constructor.
+ */
+ private Path(String device, String[] segments, int _separators) {
+ // no segment validations are done for performance reasons
+ this.segments = segments;
+ this.device = device;
+ //hash code is cached in all but the bottom three bits of the separators field
+ this.separators = (computeHashCode() << 3) | (_separators & ALL_SEPARATORS);
+ }
+
+ /* (Intentionally not included in javadoc)
+ * @see IPath#addFileExtension
+ */
+ public IPath addFileExtension(String extension) {
+ if (isRoot() || isEmpty() || hasTrailingSeparator())
+ return this;
+ int len = segments.length;
+ String[] newSegments = new String[len];
+ System.arraycopy(segments, 0, newSegments, 0, len - 1);
+ newSegments[len - 1] = segments[len - 1] + '.' + extension;
+ return new Path(device, newSegments, separators);
+ }
+
+ /* (Intentionally not included in javadoc)
+ * @see IPath#addTrailingSeparator
+ */
+ public IPath addTrailingSeparator() {
+ if (hasTrailingSeparator() || isRoot()) {
+ return this;
+ }
+ //XXX workaround, see 1GIGQ9V
+ if (isEmpty()) {
+ return new Path(device, segments, HAS_LEADING);
+ }
+ return new Path(device, segments, separators | HAS_TRAILING);
+ }
+
+ /* (Intentionally not included in javadoc)
+ * @see IPath#append(IPath)
+ */
+ public IPath append(IPath tail) {
+ //optimize some easy cases
+ if (tail == null || tail.segmentCount() == 0)
+ return this;
+ if (this.isEmpty())
+ return tail.setDevice(device).makeRelative();
+ if (this.isRoot())
+ return tail.setDevice(device).makeAbsolute();
+
+ //concatenate the two segment arrays
+ int myLen = segments.length;
+ int tailLen = tail.segmentCount();
+ String[] newSegments = new String[myLen + tailLen];
+ System.arraycopy(segments, 0, newSegments, 0, myLen);
+ for (int i = 0; i < tailLen; i++) {
+ newSegments[myLen + i] = tail.segment(i);
+ }
+ //use my leading separators and the tail's trailing separator
+ Path result = new Path(device, newSegments, (separators & (HAS_LEADING | IS_UNC)) | (tail.hasTrailingSeparator() ? HAS_TRAILING : 0));
+ String tailFirstSegment = newSegments[myLen];
+ if (tailFirstSegment.equals("..") || tailFirstSegment.equals(".")) { //$NON-NLS-1$ //$NON-NLS-2$
+ result.canonicalize();
+ }
+ return result;
+ }
+
+ /* (Intentionally not included in javadoc)
+ * @see IPath#append(java.lang.String)
+ */
+ public IPath append(String tail) {
+ //optimize addition of a single segment
+ if (tail.indexOf(SEPARATOR) == -1 && tail.indexOf("\\") == -1 && tail.indexOf(DEVICE_SEPARATOR) == -1) { //$NON-NLS-1$
+ int tailLength = tail.length();
+ if (tailLength < 3) {
+ //some special cases
+ if (tailLength == 0 || ".".equals(tail)) { //$NON-NLS-1$
+ return this;
+ }
+ if ("..".equals(tail)) //$NON-NLS-1$
+ return removeLastSegments(1);
+ }
+ //just add the segment
+ int myLen = segments.length;
+ String[] newSegments = new String[myLen + 1];
+ System.arraycopy(segments, 0, newSegments, 0, myLen);
+ newSegments[myLen] = tail;
+ return new Path(device, newSegments, separators & ~HAS_TRAILING);
+ }
+ //go with easy implementation
+ return append(new Path(tail));
+ }
+
+ /**
+ * Destructively converts this path to its canonical form.
+ * <p>
+ * In its canonical form, a path does not have any
+ * "." segments, and parent references ("..") are collapsed
+ * where possible.
+ * </p>
+ * @return true if the path was modified, and false otherwise.
+ */
+ private boolean canonicalize() {
+ //look for segments that need canonicalizing
+ for (int i = 0, max = segments.length; i < max; i++) {
+ String segment = segments[i];
+ if (segment.charAt(0) == '.' && (segment.equals("..") || segment.equals("."))) { //$NON-NLS-1$ //$NON-NLS-2$
+ //path needs to be canonicalized
+ collapseParentReferences();
+ //paths of length 0 have no trailing separator
+ if (segments.length == 0)
+ separators &= (HAS_LEADING | IS_UNC);
+ //recompute hash because canonicalize affects hash
+ separators = (separators & ALL_SEPARATORS) | (computeHashCode() << 3);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /* (Intentionally not included in javadoc)
+ * Clones this object.
+ */
+ public Object clone() {
+ try {
+ return super.clone();
+ } catch (CloneNotSupportedException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Destructively removes all occurrences of ".." segments from this path.
+ */
+ private void collapseParentReferences() {
+ int segmentCount = segments.length;
+ String[] stack = new String[segmentCount];
+ int stackPointer = 0;
+ for (int i = 0; i < segmentCount; i++) {
+ String segment = segments[i];
+ if (segment.equals("..")) { //$NON-NLS-1$
+ if (stackPointer == 0) {
+ // if the stack is empty we are going out of our scope
+ // so we need to accumulate segments. But only if the original
+ // path is relative. If it is absolute then we can't go any higher than
+ // root so simply toss the .. references.
+ if (!isAbsolute())
+ stack[stackPointer++] = segment; //stack push
+ } else {
+ // if the top is '..' then we are accumulating segments so don't pop
+ if ("..".equals(stack[stackPointer - 1])) //$NON-NLS-1$
+ stack[stackPointer++] = ".."; //$NON-NLS-1$
+ else
+ stackPointer--;
+ //stack pop
+ }
+ //collapse current references
+ } else if (!segment.equals(".") || (i == 0 && !isAbsolute())) //$NON-NLS-1$
+ stack[stackPointer++] = segment; //stack push
+ }
+ //if the number of segments hasn't changed, then no modification needed
+ if (stackPointer == segmentCount)
+ return;
+ //build the new segment array backwards by popping the stack
+ String[] newSegments = new String[stackPointer];
+ System.arraycopy(stack, 0, newSegments, 0, stackPointer);
+ this.segments = newSegments;
+ }
+
+ /**
+ * Removes duplicate slashes from the given path, with the exception
+ * of leading double slash which represents a UNC path.
+ */
+ private String collapseSlashes(String path) {
+ int length = path.length();
+ // if the path is only 0, 1 or 2 chars long then it could not possibly have illegal
+ // duplicate slashes.
+ if (length < 3)
+ return path;
+ // check for an occurrence of // in the path. Start at index 1 to ensure we skip leading UNC //
+ // If there are no // then there is nothing to collapse so just return.
+ if (path.indexOf("//", 1) == -1) //$NON-NLS-1$
+ return path;
+ // We found an occurrence of // in the path so do the slow collapse.
+ char[] result = new char[path.length()];
+ int count = 0;
+ boolean hasPrevious = false;
+ char[] characters = path.toCharArray();
+ for (int index = 0; index < characters.length; index++) {
+ char c = characters[index];
+ if (c == SEPARATOR) {
+ if (hasPrevious) {
+ // skip double slashes, except for beginning of UNC.
+ // note that a UNC path can't have a device.
+ if (device == null && index == 1) {
+ result[count] = c;
+ count++;
+ }
+ } else {
+ hasPrevious = true;
+ result[count] = c;
+ count++;
+ }
+ } else {
+ hasPrevious = false;
+ result[count] = c;
+ count++;
+ }
+ }
+ return new String(result, 0, count);
+ }
+
+ /* (Intentionally not included in javadoc)
+ * Computes the hash code for this object.
+ */
+ private int computeHashCode() {
+ int hash = device == null ? 17 : device.hashCode();
+ int segmentCount = segments.length;
+ for (int i = 0; i < segmentCount; i++) {
+ //this function tends to given a fairly even distribution
+ hash = hash * 37 + segments[i].hashCode();
+ }
+ return hash;
+ }
+
+ /* (Intentionally not included in javadoc)
+ * Returns the size of the string that will be created by toString or toOSString.
+ */
+ private int computeLength() {
+ int length = 0;
+ if (device != null)
+ length += device.length();
+ if ((separators & HAS_LEADING) != 0)
+ length++;
+ if ((separators & IS_UNC) != 0)
+ length++;
+ //add the segment lengths
+ int max = segments.length;
+ if (max > 0) {
+ for (int i = 0; i < max; i++) {
+ length += segments[i].length();
+ }
+ //add the separator lengths
+ length += max - 1;
+ }
+ if ((separators & HAS_TRAILING) != 0)
+ length++;
+ return length;
+ }
+
+ /* (Intentionally not included in javadoc)
+ * Returns the number of segments in the given path
+ */
+ private int computeSegmentCount(String path) {
+ int len = path.length();
+ if (len == 0 || (len == 1 && path.charAt(0) == SEPARATOR)) {
+ return 0;
+ }
+ int count = 1;
+ int prev = -1;
+ int i;
+ while ((i = path.indexOf(SEPARATOR, prev + 1)) != -1) {
+ if (i != prev + 1 && i != len) {
+ ++count;
+ }
+ prev = i;
+ }
+ if (path.charAt(len - 1) == SEPARATOR) {
+ --count;
+ }
+ return count;
+ }
+
+ /**
+ * Computes the segment array for the given canonicalized path.
+ */
+ private String[] computeSegments(String path) {
+ // performance sensitive --- avoid creating garbage
+ int segmentCount = computeSegmentCount(path);
+ if (segmentCount == 0)
+ return NO_SEGMENTS;
+ String[] newSegments = new String[segmentCount];
+ int len = path.length();
+ // check for initial slash
+ int firstPosition = (path.charAt(0) == SEPARATOR) ? 1 : 0;
+ // check for UNC
+ if (firstPosition == 1 && len > 1 && (path.charAt(1) == SEPARATOR))
+ firstPosition = 2;
+ int lastPosition = (path.charAt(len - 1) != SEPARATOR) ? len - 1 : len - 2;
+ // for non-empty paths, the number of segments is
+ // the number of slashes plus 1, ignoring any leading
+ // and trailing slashes
+ int next = firstPosition;
+ for (int i = 0; i < segmentCount; i++) {
+ int start = next;
+ int end = path.indexOf(SEPARATOR, next);
+ if (end == -1) {
+ newSegments[i] = path.substring(start, lastPosition + 1);
+ } else {
+ newSegments[i] = path.substring(start, end);
+ }
+ next = end + 1;
+ }
+ return newSegments;
+ }
+ /**
+ * Returns the platform-neutral encoding of the given segment onto
+ * the given string buffer. This escapes literal colon characters with double colons.
+ */
+ private void encodeSegment(String string, StringBuffer buf) {
+ int len = string.length();
+ for (int i = 0; i < len; i++) {
+ char c = string.charAt(i);
+ buf.append(c);
+ if (c == DEVICE_SEPARATOR)
+ buf.append(DEVICE_SEPARATOR);
+ }
+ }
+
+ /* (Intentionally not included in javadoc)
+ * Compares objects for equality.
+ */
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (!(obj instanceof Path))
+ return false;
+ Path target = (Path) obj;
+ //check leading separators and hash code
+ if ((separators & HASH_MASK) != (target.separators & HASH_MASK))
+ return false;
+ String[] targetSegments = target.segments;
+ int i = segments.length;
+ //check segment count
+ if (i != targetSegments.length)
+ return false;
+ //check segments in reverse order - later segments more likely to differ
+ while (--i >= 0)
+ if (!segments[i].equals(targetSegments[i]))
+ return false;
+ //check device last (least likely to differ)
+ return device == target.device || (device != null && device.equals(target.device));
+ }
+
+ /* (Intentionally not included in javadoc)
+ * @see IPath#getDevice
+ */
+ public String getDevice() {
+ return device;
+ }
+
+ /* (Intentionally not included in javadoc)
+ * @see IPath#getFileExtension
+ */
+ public String getFileExtension() {
+ if (hasTrailingSeparator()) {
+ return null;
+ }
+ String lastSegment = lastSegment();
+ if (lastSegment == null) {
+ return null;
+ }
+ int index = lastSegment.lastIndexOf('.');
+ if (index == -1) {
+ return null;
+ }
+ return lastSegment.substring(index + 1);
+ }
+
+ /* (Intentionally not included in javadoc)
+ * Computes the hash code for this object.
+ */
+ public int hashCode() {
+ return separators & HASH_MASK;
+ }
+
+ /* (Intentionally not included in javadoc)
+ * @see IPath#hasTrailingSeparator2
+ */
+ public boolean hasTrailingSeparator() {
+ return (separators & HAS_TRAILING) != 0;
+ }
+
+ /*
+ * Initialize the current path with the given string.
+ */
+ private IPath initialize(String deviceString, String path) {
+ Assert.isNotNull(path);
+ this.device = deviceString;
+
+ path = collapseSlashes(path);
+ int len = path.length();
+
+ //compute the separators array
+ if (len < 2) {
+ if (len == 1 && path.charAt(0) == SEPARATOR) {
+ this.separators = HAS_LEADING;
+ } else {
+ this.separators = 0;
+ }
+ } else {
+ boolean hasLeading = path.charAt(0) == SEPARATOR;
+ boolean isUNC = hasLeading && path.charAt(1) == SEPARATOR;
+ //UNC path of length two has no trailing separator
+ boolean hasTrailing = !(isUNC && len == 2) && path.charAt(len - 1) == SEPARATOR;
+ separators = hasLeading ? HAS_LEADING : 0;
+ if (isUNC)
+ separators |= IS_UNC;
+ if (hasTrailing)
+ separators |= HAS_TRAILING;
+ }
+ //compute segments and ensure canonical form
+ segments = computeSegments(path);
+ if (!canonicalize()) {
+ //compute hash now because canonicalize didn't need to do it
+ separators = (separators & ALL_SEPARATORS) | (computeHashCode() << 3);
+ }
+ return this;
+ }
+
+ /* (Intentionally not included in javadoc)
+ * @see IPath#isAbsolute
+ */
+ public boolean isAbsolute() {
+ //it's absolute if it has a leading separator
+ return (separators & HAS_LEADING) != 0;
+ }
+
+ /* (Intentionally not included in javadoc)
+ * @see IPath#isEmpty
+ */
+ public boolean isEmpty() {
+ //true if no segments and no leading prefix
+ return segments.length == 0 && ((separators & ALL_SEPARATORS) != HAS_LEADING);
+
+ }
+
+ /* (Intentionally not included in javadoc)
+ * @see IPath#isPrefixOf
+ */
+ public boolean isPrefixOf(IPath anotherPath) {
+ if (device == null) {
+ if (anotherPath.getDevice() != null) {
+ return false;
+ }
+ } else {
+ if (!device.equalsIgnoreCase(anotherPath.getDevice())) {
+ return false;
+ }
+ }
+ if (isEmpty() || (isRoot() && anotherPath.isAbsolute())) {
+ return true;
+ }
+ int len = segments.length;
+ if (len > anotherPath.segmentCount()) {
+ return false;
+ }
+ for (int i = 0; i < len; i++) {
+ if (!segments[i].equals(anotherPath.segment(i)))
+ return false;
+ }
+ return true;
+ }
+
+ /* (Intentionally not included in javadoc)
+ * @see IPath#isRoot
+ */
+ public boolean isRoot() {
+ //must have no segments, a leading separator, and not be a UNC path.
+ return this == ROOT || (segments.length == 0 && ((separators & ALL_SEPARATORS) == HAS_LEADING));
+ }
+
+ /* (Intentionally not included in javadoc)
+ * @see IPath#isUNC
+ */
+ public boolean isUNC() {
+ if (device != null)
+ return false;
+ return (separators & IS_UNC) != 0;
+ }
+
+ /* (Intentionally not included in javadoc)
+ * @see IPath#isValidPath(String)
+ */
+ public boolean isValidPath(String path) {
+ Path test = new Path(path);
+ for (int i = 0, max = test.segmentCount(); i < max; i++)
+ if (!isValidSegment(test.segment(i)))
+ return false;
+ return true;
+ }
+
+ /* (Intentionally not included in javadoc)
+ * @see IPath#isValidSegment(String)
+ */
+ public boolean isValidSegment(String segment) {
+ int size = segment.length();
+ if (size == 0)
+ return false;
+ for (int i = 0; i < size; i++) {
+ char c = segment.charAt(i);
+ if (c == '/')
+ return false;
+ if (WINDOWS && (c == '\\' || c == ':'))
+ return false;
+ }
+ return true;
+ }
+
+ /* (Intentionally not included in javadoc)
+ * @see IPath#lastSegment()
+ */
+ public String lastSegment() {
+ int len = segments.length;
+ return len == 0 ? null : segments[len - 1];
+ }
+
+ /* (Intentionally not included in javadoc)
+ * @see IPath#makeAbsolute()
+ */
+ public IPath makeAbsolute() {
+ if (isAbsolute()) {
+ return this;
+ }
+ Path result = new Path(device, segments, separators | HAS_LEADING);
+ //may need canonicalizing if it has leading ".." or "." segments
+ if (result.segmentCount() > 0) {
+ String first = result.segment(0);
+ if (first.equals("..") || first.equals(".")) { //$NON-NLS-1$ //$NON-NLS-2$
+ result.canonicalize();
+ }
+ }
+ return result;
+ }
+
+ /* (Intentionally not included in javadoc)
+ * @see IPath#makeRelative()
+ */
+ public IPath makeRelative() {
+ if (!isAbsolute()) {
+ return this;
+ }
+ return new Path(device, segments, separators & HAS_TRAILING);
+ }
+
+ /* (Intentionally not included in javadoc)
+ * @see IPath#makeUNC(boolean)
+ */
+ public IPath makeUNC(boolean toUNC) {
+ // if we are already in the right form then just return
+ if (!(toUNC ^ isUNC()))
+ return this;
+
+ int newSeparators = this.separators;
+ if (toUNC) {
+ newSeparators |= HAS_LEADING | IS_UNC;
+ } else {
+ //mask out the UNC bit
+ newSeparators &= HAS_LEADING | HAS_TRAILING;
+ }
+ return new Path(toUNC ? null : device, segments, newSeparators);
+ }
+
+ /* (Intentionally not included in javadoc)
+ * @see IPath#matchingFirstSegments(IPath)
+ */
+ public int matchingFirstSegments(IPath anotherPath) {
+ Assert.isNotNull(anotherPath);
+ int anotherPathLen = anotherPath.segmentCount();
+ int max = Math.min(segments.length, anotherPathLen);
+ int count = 0;
+ for (int i = 0; i < max; i++) {
+ if (!segments[i].equals(anotherPath.segment(i))) {
+ return count;
+ }
+ count++;
+ }
+ return count;
+ }
+
+ /* (Intentionally not included in javadoc)
+ * @see IPath#removeFileExtension()
+ */
+ public IPath removeFileExtension() {
+ String extension = getFileExtension();
+ if (extension == null || extension.equals("")) { //$NON-NLS-1$
+ return this;
+ }
+ String lastSegment = lastSegment();
+ int index = lastSegment.lastIndexOf(extension) - 1;
+ return removeLastSegments(1).append(lastSegment.substring(0, index));
+ }
+
+ /* (Intentionally not included in javadoc)
+ * @see IPath#removeFirstSegments(int)
+ */
+ public IPath removeFirstSegments(int count) {
+ if (count == 0)
+ return this;
+ if (count >= segments.length) {
+ return new Path(device, NO_SEGMENTS, 0);
+ }
+ Assert.isLegal(count > 0);
+ int newSize = segments.length - count;
+ String[] newSegments = new String[newSize];
+ System.arraycopy(this.segments, count, newSegments, 0, newSize);
+
+ //result is always a relative path
+ return new Path(device, newSegments, separators & HAS_TRAILING);
+ }
+
+ /* (Intentionally not included in javadoc)
+ * @see IPath#removeLastSegments(int)
+ */
+ public IPath removeLastSegments(int count) {
+ if (count == 0)
+ return this;
+ if (count >= segments.length) {
+ //result will have no trailing separator
+ return new Path(device, NO_SEGMENTS, separators & (HAS_LEADING | IS_UNC));
+ }
+ Assert.isLegal(count > 0);
+ int newSize = segments.length - count;
+ String[] newSegments = new String[newSize];
+ System.arraycopy(this.segments, 0, newSegments, 0, newSize);
+ return new Path(device, newSegments, separators);
+ }
+
+ /* (Intentionally not included in javadoc)
+ * @see IPath#removeTrailingSeparator()
+ */
+ public IPath removeTrailingSeparator() {
+ if (!hasTrailingSeparator()) {
+ return this;
+ }
+ return new Path(device, segments, separators & (HAS_LEADING | IS_UNC));
+ }
+
+ /* (Intentionally not included in javadoc)
+ * @see IPath#segment(int)
+ */
+ public String segment(int index) {
+ if (index >= segments.length)
+ return null;
+ return segments[index];
+ }
+
+ /* (Intentionally not included in javadoc)
+ * @see IPath#segmentCount()
+ */
+ public int segmentCount() {
+ return segments.length;
+ }
+
+ /* (Intentionally not included in javadoc)
+ * @see IPath#segments()
+ */
+ public String[] segments() {
+ String[] segmentCopy = new String[segments.length];
+ System.arraycopy(segments, 0, segmentCopy, 0, segments.length);
+ return segmentCopy;
+ }
+
+ /* (Intentionally not included in javadoc)
+ * @see IPath#setDevice(String)
+ */
+ public IPath setDevice(String value) {
+ if (value != null) {
+ Assert.isTrue(value.indexOf(IPath.DEVICE_SEPARATOR) == (value.length() - 1), "Last character should be the device separator"); //$NON-NLS-1$
+ }
+ //return the receiver if the device is the same
+ if (value == device || (value != null && value.equals(device)))
+ return this;
+
+ return new Path(value, segments, separators);
+ }
+
+ /* (Intentionally not included in javadoc)
+ * @see IPath#toFile()
+ */
+ public File toFile() {
+ return new File(toOSString());
+ }
+
+ /* (Intentionally not included in javadoc)
+ * @see IPath#toOSString()
+ */
+ public String toOSString() {
+ //Note that this method is identical to toString except
+ //it uses the OS file separator instead of the path separator
+ int resultSize = computeLength();
+ if (resultSize <= 0)
+ return EMPTY_STRING;
+ char FILE_SEPARATOR = File.separatorChar;
+ char[] result = new char[resultSize];
+ int offset = 0;
+ if (device != null) {
+ int size = device.length();
+ device.getChars(0, size, result, offset);
+ offset += size;
+ }
+ if ((separators & HAS_LEADING) != 0)
+ result[offset++] = FILE_SEPARATOR;
+ if ((separators & IS_UNC) != 0)
+ result[offset++] = FILE_SEPARATOR;
+ int len = segments.length - 1;
+ if (len >= 0) {
+ //append all but the last segment, with separators
+ for (int i = 0; i < len; i++) {
+ int size = segments[i].length();
+ segments[i].getChars(0, size, result, offset);
+ offset += size;
+ result[offset++] = FILE_SEPARATOR;
+ }
+ //append the last segment
+ int size = segments[len].length();
+ segments[len].getChars(0, size, result, offset);
+ offset += size;
+ }
+ if ((separators & HAS_TRAILING) != 0)
+ result[offset++] = FILE_SEPARATOR;
+ return new String(result);
+ }
+
+ /* (Intentionally not included in javadoc)
+ * @see IPath#toPortableString()
+ */
+ public String toPortableString() {
+ int resultSize = computeLength();
+ if (resultSize <= 0)
+ return EMPTY_STRING;
+ StringBuffer result = new StringBuffer(resultSize);
+ if (device != null)
+ result.append(device);
+ if ((separators & HAS_LEADING) != 0)
+ result.append(SEPARATOR);
+ if ((separators & IS_UNC) != 0)
+ result.append(SEPARATOR);
+ int len = segments.length;
+ //append all segments with separators
+ for (int i = 0; i < len; i++) {
+ if (segments[i].indexOf(DEVICE_SEPARATOR) >= 0)
+ encodeSegment(segments[i], result);
+ else
+ result.append(segments[i]);
+ if (i < len-1 || (separators & HAS_TRAILING) != 0)
+ result.append(SEPARATOR);
+ }
+ return result.toString();
+ }
+
+ /* (Intentionally not included in javadoc)
+ * @see IPath#toString()
+ */
+ public String toString() {
+ int resultSize = computeLength();
+ if (resultSize <= 0)
+ return EMPTY_STRING;
+ char[] result = new char[resultSize];
+ int offset = 0;
+ if (device != null) {
+ int size = device.length();
+ device.getChars(0, size, result, offset);
+ offset += size;
+ }
+ if ((separators & HAS_LEADING) != 0)
+ result[offset++] = SEPARATOR;
+ if ((separators & IS_UNC) != 0)
+ result[offset++] = SEPARATOR;
+ int len = segments.length - 1;
+ if (len >= 0) {
+ //append all but the last segment, with separators
+ for (int i = 0; i < len; i++) {
+ int size = segments[i].length();
+ segments[i].getChars(0, size, result, offset);
+ offset += size;
+ result[offset++] = SEPARATOR;
+ }
+ //append the last segment
+ int size = segments[len].length();
+ segments[len].getChars(0, size, result, offset);
+ offset += size;
+ }
+ if ((separators & HAS_TRAILING) != 0)
+ result[offset++] = SEPARATOR;
+ return new String(result);
+ }
+
+ /* (Intentionally not included in javadoc)
+ * @see IPath#uptoSegment(int)
+ */
+ public IPath uptoSegment(int count) {
+ if (count == 0)
+ return new Path(device, NO_SEGMENTS, separators & (HAS_LEADING | IS_UNC));
+ if (count >= segments.length)
+ return this;
+ Assert.isTrue(count > 0, "Invalid parameter to Path.uptoSegment"); //$NON-NLS-1$
+ String[] newSegments = new String[count];
+ System.arraycopy(segments, 0, newSegments, 0, count);
+ return new Path(device, newSegments, separators);
+ }
+}
\ No newline at end of file
diff --git a/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/PlatformObject.java b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/PlatformObject.java
new file mode 100644
index 0000000..5b2e5c8
--- /dev/null
+++ b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/PlatformObject.java
@@ -0,0 +1,67 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 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.runtime;
+
+import org.eclipse.core.internal.runtime.AdapterManager;
+
+/**
+ * An abstract superclass implementing the <code>IAdaptable</code>
+ * interface. <code>getAdapter</code> invocations are directed
+ * to the platform's adapter manager.
+ * <p>
+ * Note: In situations where it would be awkward to subclass this
+ * class, the same affect can be achieved simply by implementing
+ * the <code>IAdaptable</code> interface and explicitly forwarding
+ * the <code>getAdapter</code> request to the platform's
+ * adapter manager. The method would look like:
+ * <pre>
+ * public Object getAdapter(Class adapter) {
+ * return Platform.getAdapterManager().getAdapter(this, adapter);
+ * }
+ * </pre>
+ * </p>
+ * <p>
+ * Clients may subclass.
+ * </p>
+ *
+ * @see Platform#getAdapterManager()
+ * @see IAdaptable
+ */
+public abstract class PlatformObject implements IAdaptable {
+ /**
+ * Constructs a new platform object.
+ */
+ public PlatformObject() {
+ super();
+ }
+
+ /**
+ * Returns an object which is an instance of the given class
+ * associated with this object. Returns <code>null</code> if
+ * no such object can be found.
+ * <p>
+ * This implementation of the method declared by <code>IAdaptable</code>
+ * passes the request along to the platform's adapter manager; roughly
+ * <code>Platform.getAdapterManager().getAdapter(this, adapter)</code>.
+ * Subclasses may override this method (however, if they do so, they
+ * should invoke the method on their superclass to ensure that the
+ * Platform's adapter manager is consulted).
+ * </p>
+ *
+ * @param adapter the class to adapt to
+ * @return the adapted object or <code>null</code>
+ * @see IAdaptable#getAdapter(Class)
+ * @see Platform#getAdapterManager()
+ */
+ public Object getAdapter(Class adapter) {
+ return AdapterManager.getDefault().getAdapter(this, adapter);
+ }
+}
diff --git a/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/PluginVersionIdentifier.java b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/PluginVersionIdentifier.java
new file mode 100644
index 0000000..737574a
--- /dev/null
+++ b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/PluginVersionIdentifier.java
@@ -0,0 +1,485 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2005 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.runtime;
+
+import java.util.StringTokenizer;
+import java.util.Vector;
+import org.eclipse.core.internal.runtime.CommonMessages;
+import org.eclipse.core.internal.runtime.IRuntimeConstants;
+import org.eclipse.osgi.util.NLS;
+
+/**
+ * <p>
+ * Version identifier for a plug-in. In its string representation,
+ * it consists of up to 4 tokens separated by a decimal point.
+ * The first 3 tokens are positive integer numbers, the last token
+ * is an uninterpreted string (no whitespace characters allowed).
+ * For example, the following are valid version identifiers
+ * (as strings):
+ * <ul>
+ * <li><code>0.0.0</code></li>
+ * <li><code>1.0.127564</code></li>
+ * <li><code>3.7.2.build-127J</code></li>
+ * <li><code>1.9</code> (interpreted as <code>1.9.0</code>)</li>
+ * <li><code>3</code> (interpreted as <code>3.0.0</code>)</li>
+ * </ul>
+ * </p>
+ * <p>
+ * The version identifier can be decomposed into a major, minor,
+ * service level component and qualifier components. A difference
+ * in the major component is interpreted as an incompatible version
+ * change. A difference in the minor (and not the major) component
+ * is interpreted as a compatible version change. The service
+ * level component is interpreted as a cumulative and compatible
+ * service update of the minor version component. The qualifier is
+ * not interpreted, other than in version comparisons. The
+ * qualifiers are compared using lexicographical string comparison.
+ * </p>
+ * <p>
+ * Version identifiers can be matched as perfectly equal, equivalent,
+ * compatible or greaterOrEqual.
+ * </p>
+ * <p>
+ * Clients may instantiate; not intended to be subclassed by clients.
+ * </p>
+ * @see java.lang.String#compareTo(java.lang.String)
+ */
+
+// XXX consider deprecating in favour of org.osgi.framework.Version.
+// if deprecated then move this class to the compatibility plugin.
+public final class PluginVersionIdentifier {
+
+ private int major = 0;
+ private int minor = 0;
+ private int service = 0;
+ private String qualifier = ""; //$NON-NLS-1$
+
+ private static final String SEPARATOR = "."; //$NON-NLS-1$
+
+ /**
+ * Creates a plug-in version identifier from its components.
+ *
+ * @param major major component of the version identifier
+ * @param minor minor component of the version identifier
+ * @param service service update component of the version identifier
+ */
+ public PluginVersionIdentifier(int major, int minor, int service) {
+ this(major, minor, service, null);
+ }
+
+ /**
+ * Creates a plug-in version identifier from its components.
+ *
+ * @param major major component of the version identifier
+ * @param minor minor component of the version identifier
+ * @param service service update component of the version identifier
+ * @param qualifier qualifier component of the version identifier.
+ * Qualifier characters that are not a letter or a digit are replaced.
+ */
+ public PluginVersionIdentifier(int major, int minor, int service, String qualifier) {
+
+ // Do the test outside of the assert so that they 'Policy.bind'
+ // will not be evaluated each time (including cases when we would
+ // have passed by the assert).
+
+ if (major < 0)
+ Assert.isTrue(false, NLS.bind(CommonMessages.parse_postiveMajor, major + SEPARATOR + minor + SEPARATOR + service + SEPARATOR + qualifier));
+ if (minor < 0)
+ Assert.isTrue(false, NLS.bind(CommonMessages.parse_postiveMinor, major + SEPARATOR + minor + SEPARATOR + service + SEPARATOR + qualifier));
+ if (service < 0)
+ Assert.isTrue(false, NLS.bind(CommonMessages.parse_postiveService, major + SEPARATOR + minor + SEPARATOR + service + SEPARATOR + qualifier));
+ if (qualifier == null)
+ qualifier = ""; //$NON-NLS-1$
+
+ this.major = major;
+ this.minor = minor;
+ this.service = service;
+ this.qualifier = verifyQualifier(qualifier);
+ }
+
+ /**
+ * Creates a plug-in version identifier from the given string.
+ * The string representation consists of up to 4 tokens
+ * separated by decimal point.
+ * For example, the following are valid version identifiers
+ * (as strings):
+ * <ul>
+ * <li><code>0.0.0</code></li>
+ * <li><code>1.0.127564</code></li>
+ * <li><code>3.7.2.build-127J</code></li>
+ * <li><code>1.9</code> (interpreted as <code>1.9.0</code>)</li>
+ * <li><code>3</code> (interpreted as <code>3.0.0</code>)</li>
+ * </ul>
+ * </p>
+ *
+ * @param versionId string representation of the version identifier.
+ * Qualifier characters that are not a letter or a digit are replaced.
+ */
+ public PluginVersionIdentifier(String versionId) {
+ Object[] parts = parseVersion(versionId);
+ this.major = ((Integer) parts[0]).intValue();
+ this.minor = ((Integer) parts[1]).intValue();
+ this.service = ((Integer) parts[2]).intValue();
+ this.qualifier = (String) parts[3];
+ }
+
+ /**
+ * Validates the given string as a plug-in version identifier.
+ *
+ * @param version the string to validate
+ * @return a status object with code <code>IStatus.OK</code> if
+ * the given string is valid as a plug-in version identifier, otherwise a status
+ * object indicating what is wrong with the string
+ * @since 2.0
+ */
+ public static IStatus validateVersion(String version) {
+ try {
+ parseVersion(version);
+ } catch (RuntimeException e) {
+ return new Status(IStatus.ERROR, IRuntimeConstants.PI_RUNTIME, IStatus.ERROR, e.getMessage(), e);
+ }
+ return Status.OK_STATUS;
+ }
+
+ private static Object[] parseVersion(String versionId) {
+
+ // Do the test outside of the assert so that they 'Policy.bind'
+ // will not be evaluated each time (including cases when we would
+ // have passed by the assert).
+ if (versionId == null)
+ Assert.isNotNull(null, CommonMessages.parse_emptyPluginVersion);
+ String s = versionId.trim();
+ if (s.equals("")) //$NON-NLS-1$
+ Assert.isTrue(false, CommonMessages.parse_emptyPluginVersion);
+ if (s.startsWith(SEPARATOR))
+ Assert.isTrue(false, NLS.bind(CommonMessages.parse_separatorStartVersion, s));
+ if (s.endsWith(SEPARATOR))
+ Assert.isTrue(false, NLS.bind(CommonMessages.parse_separatorEndVersion, s));
+ if (s.indexOf(SEPARATOR + SEPARATOR) != -1)
+ Assert.isTrue(false, NLS.bind(CommonMessages.parse_doubleSeparatorVersion, s));
+
+ StringTokenizer st = new StringTokenizer(s, SEPARATOR);
+ Vector elements = new Vector(4);
+
+ while (st.hasMoreTokens())
+ elements.addElement(st.nextToken());
+
+ int elementSize = elements.size();
+
+ if (elementSize <= 0)
+ Assert.isTrue(false, NLS.bind(CommonMessages.parse_oneElementPluginVersion, s));
+ if (elementSize > 4)
+ Assert.isTrue(false, NLS.bind(CommonMessages.parse_fourElementPluginVersion, s));
+
+ int[] numbers = new int[3];
+ try {
+ numbers[0] = Integer.parseInt((String) elements.elementAt(0));
+ if (numbers[0] < 0)
+ Assert.isTrue(false, NLS.bind(CommonMessages.parse_postiveMajor, s));
+ } catch (NumberFormatException nfe) {
+ Assert.isTrue(false, NLS.bind(CommonMessages.parse_numericMajorComponent, s));
+ }
+
+ try {
+ if (elementSize >= 2) {
+ numbers[1] = Integer.parseInt((String) elements.elementAt(1));
+ if (numbers[1] < 0)
+ Assert.isTrue(false, NLS.bind(CommonMessages.parse_postiveMinor, s));
+ } else
+ numbers[1] = 0;
+ } catch (NumberFormatException nfe) {
+ Assert.isTrue(false, NLS.bind(CommonMessages.parse_numericMinorComponent, s));
+ }
+
+ try {
+ if (elementSize >= 3) {
+ numbers[2] = Integer.parseInt((String) elements.elementAt(2));
+ if (numbers[2] < 0)
+ Assert.isTrue(false, NLS.bind(CommonMessages.parse_postiveService, s));
+ } else
+ numbers[2] = 0;
+ } catch (NumberFormatException nfe) {
+ Assert.isTrue(false, NLS.bind(CommonMessages.parse_numericServiceComponent, s));
+ }
+
+ // "result" is a 4-element array with the major, minor, service, and qualifier
+ Object[] result = new Object[4];
+ result[0] = new Integer(numbers[0]);
+ result[1] = new Integer(numbers[1]);
+ result[2] = new Integer(numbers[2]);
+ if (elementSize >= 4)
+ result[3] = verifyQualifier((String) elements.elementAt(3));
+ else
+ result[3] = ""; //$NON-NLS-1$
+ return result;
+ }
+
+ /**
+ * Compare version identifiers for equality. Identifiers are
+ * equal if all of their components are equal.
+ *
+ * @param object an object to compare
+ * @return whether or not the two objects are equal
+ */
+ public boolean equals(Object object) {
+ if (!(object instanceof PluginVersionIdentifier))
+ return false;
+ PluginVersionIdentifier v = (PluginVersionIdentifier) object;
+ return v.getMajorComponent() == major && v.getMinorComponent() == minor && v.getServiceComponent() == service && v.getQualifierComponent().equals(qualifier);
+ }
+
+ /**
+ * Returns a hash code value for the object.
+ *
+ * @return an integer which is a hash code value for this object.
+ */
+ public int hashCode() {
+ int code = major + minor + service; // R1.0 result
+ if (qualifier.equals("")) //$NON-NLS-1$
+ return code;
+ else
+ return code + qualifier.hashCode();
+ }
+
+ /**
+ * Returns the major (incompatible) component of this
+ * version identifier.
+ *
+ * @return the major version
+ */
+ public int getMajorComponent() {
+ return major;
+ }
+
+ /**
+ * Returns the minor (compatible) component of this
+ * version identifier.
+ *
+ * @return the minor version
+ */
+ public int getMinorComponent() {
+ return minor;
+ }
+
+ /**
+ * Returns the service level component of this
+ * version identifier.
+ *
+ * @return the service level
+ */
+ public int getServiceComponent() {
+ return service;
+ }
+
+ /**
+ * Returns the qualifier component of this
+ * version identifier.
+ *
+ * @return the qualifier
+ */
+ public String getQualifierComponent() {
+ return qualifier;
+ }
+
+ /**
+ * Compares two version identifiers to see if this one is
+ * greater than or equal to the argument.
+ * <p>
+ * A version identifier is considered to be greater than or equal
+ * if its major component is greater than the argument major
+ * component, or the major components are equal and its minor component
+ * is greater than the argument minor component, or the
+ * major and minor components are equal and its service component is
+ * greater than the argument service component, or the major, minor and
+ * service components are equal and the qualifier component is
+ * greater than the argument qualifier component (using lexicographic
+ * string comparison), or all components are equal.
+ * </p>
+ *
+ * @param id the other version identifier
+ * @return <code>true</code> is this version identifier
+ * is compatible with the given version identifier, and
+ * <code>false</code> otherwise
+ * @since 2.0
+ */
+ public boolean isGreaterOrEqualTo(PluginVersionIdentifier id) {
+ if (id == null)
+ return false;
+ if (major > id.getMajorComponent())
+ return true;
+ if ((major == id.getMajorComponent()) && (minor > id.getMinorComponent()))
+ return true;
+ if ((major == id.getMajorComponent()) && (minor == id.getMinorComponent()) && (service > id.getServiceComponent()))
+ return true;
+ if ((major == id.getMajorComponent()) && (minor == id.getMinorComponent()) && (service == id.getServiceComponent()) && (qualifier.compareTo(id.getQualifierComponent()) >= 0))
+ return true;
+ else
+ return false;
+ }
+
+ /**
+ * Compares two version identifiers for compatibility.
+ * <p>
+ * A version identifier is considered to be compatible if its major
+ * component equals to the argument major component, and its minor component
+ * is greater than or equal to the argument minor component.
+ * If the minor components are equal, than the service level of the
+ * version identifier must be greater than or equal to the service level
+ * of the argument identifier. If the service levels are equal, the two
+ * version identifiers are considered to be equivalent if this qualifier is
+ * greater or equal to the qualifier of the argument (using lexicographic
+ * string comparison).
+ * </p>
+ *
+ * @param id the other version identifier
+ * @return <code>true</code> is this version identifier
+ * is compatible with the given version identifier, and
+ * <code>false</code> otherwise
+ */
+ public boolean isCompatibleWith(PluginVersionIdentifier id) {
+ if (id == null)
+ return false;
+ if (major != id.getMajorComponent())
+ return false;
+ if (minor > id.getMinorComponent())
+ return true;
+ if (minor < id.getMinorComponent())
+ return false;
+ if (service > id.getServiceComponent())
+ return true;
+ if (service < id.getServiceComponent())
+ return false;
+ if (qualifier.compareTo(id.getQualifierComponent()) >= 0)
+ return true;
+ else
+ return false;
+ }
+
+ /**
+ * Compares two version identifiers for equivalency.
+ * <p>
+ * Two version identifiers are considered to be equivalent if their major
+ * and minor component equal and are at least at the same service level
+ * as the argument. If the service levels are equal, the two version
+ * identifiers are considered to be equivalent if this qualifier is
+ * greater or equal to the qualifier of the argument (using lexicographic
+ * string comparison).
+ *
+ * </p>
+ *
+ * @param id the other version identifier
+ * @return <code>true</code> is this version identifier
+ * is equivalent to the given version identifier, and
+ * <code>false</code> otherwise
+ */
+ public boolean isEquivalentTo(PluginVersionIdentifier id) {
+ if (id == null)
+ return false;
+ if (major != id.getMajorComponent())
+ return false;
+ if (minor != id.getMinorComponent())
+ return false;
+ if (service > id.getServiceComponent())
+ return true;
+ if (service < id.getServiceComponent())
+ return false;
+ if (qualifier.compareTo(id.getQualifierComponent()) >= 0)
+ return true;
+ else
+ return false;
+ }
+
+ /**
+ * Compares two version identifiers for perfect equality.
+ * <p>
+ * Two version identifiers are considered to be perfectly equal if their
+ * major, minor, service and qualifier components are equal
+ * </p>
+ *
+ * @param id the other version identifier
+ * @return <code>true</code> is this version identifier
+ * is perfectly equal to the given version identifier, and
+ * <code>false</code> otherwise
+ * @since 2.0
+ */
+ public boolean isPerfect(PluginVersionIdentifier id) {
+ if (id == null)
+ return false;
+ if ((major != id.getMajorComponent()) || (minor != id.getMinorComponent()) || (service != id.getServiceComponent()) || (!qualifier.equals(id.getQualifierComponent())))
+ return false;
+ else
+ return true;
+ }
+
+ /**
+ * Compares two version identifiers for order using multi-decimal
+ * comparison.
+ *
+ * @param id the other version identifier
+ * @return <code>true</code> is this version identifier
+ * is greater than the given version identifier, and
+ * <code>false</code> otherwise
+ */
+ public boolean isGreaterThan(PluginVersionIdentifier id) {
+
+ if (id == null) {
+ if (major == 0 && minor == 0 && service == 0 && qualifier.equals("")) //$NON-NLS-1$
+ return false;
+ else
+ return true;
+ }
+
+ if (major > id.getMajorComponent())
+ return true;
+ if (major < id.getMajorComponent())
+ return false;
+ if (minor > id.getMinorComponent())
+ return true;
+ if (minor < id.getMinorComponent())
+ return false;
+ if (service > id.getServiceComponent())
+ return true;
+ if (service < id.getServiceComponent())
+ return false;
+ if (qualifier.compareTo(id.getQualifierComponent()) > 0)
+ return true;
+ else
+ return false;
+
+ }
+
+ /**
+ * Returns the string representation of this version identifier.
+ * The result satisfies
+ * <code>vi.equals(new PluginVersionIdentifier(vi.toString()))</code>.
+ *
+ * @return the string representation of this plug-in version identifier
+ */
+ public String toString() {
+ String base = major + SEPARATOR + minor + SEPARATOR + service; // R1.0 result
+ if (qualifier.equals("")) //$NON-NLS-1$
+ return base;
+ else
+ return base + SEPARATOR + qualifier;
+ }
+
+ private static String verifyQualifier(String s) {
+ char[] chars = s.trim().toCharArray();
+ boolean whitespace = false;
+ for (int i = 0; i < chars.length; i++) {
+ if (!Character.isLetterOrDigit(chars[i])) {
+ chars[i] = '-';
+ whitespace = true;
+ }
+ }
+ return whitespace ? new String(chars) : s;
+ }
+}
diff --git a/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/ProgressMonitorWrapper.java b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/ProgressMonitorWrapper.java
new file mode 100644
index 0000000..5b7117c
--- /dev/null
+++ b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/ProgressMonitorWrapper.java
@@ -0,0 +1,168 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 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.runtime;
+
+/**
+ * An abstract wrapper around a progress monitor which,
+ * unless overridden, forwards <code>IProgressMonitor</code>
+ * and <code>IProgressMonitorWithBlocking</code> methods to the wrapped progress monitor.
+ * <p>
+ * Clients may subclass.
+ * </p>
+ */
+public abstract class ProgressMonitorWrapper implements IProgressMonitor, IProgressMonitorWithBlocking {
+
+ /** The wrapped progress monitor. */
+ private IProgressMonitor progressMonitor;
+
+ /**
+ * Creates a new wrapper around the given monitor.
+ *
+ * @param monitor the progress monitor to forward to
+ */
+ protected ProgressMonitorWrapper(IProgressMonitor monitor) {
+ Assert.isNotNull(monitor);
+ progressMonitor = monitor;
+ }
+
+ /**
+ * This implementation of a <code>IProgressMonitor</code>
+ * method forwards to the wrapped progress monitor.
+ * Clients may override this method to do additional
+ * processing.
+ *
+ * @see IProgressMonitor#beginTask(String, int)
+ */
+ public void beginTask(String name, int totalWork) {
+ progressMonitor.beginTask(name, totalWork);
+ }
+
+ /**
+ * This implementation of a <code>IProgressMonitorWithBlocking</code>
+ * method forwards to the wrapped progress monitor.
+ * Clients may override this method to do additional
+ * processing.
+ *
+ * @see IProgressMonitorWithBlocking#clearBlocked()
+ * @since 3.0
+ */
+ public void clearBlocked() {
+ if (progressMonitor instanceof IProgressMonitorWithBlocking)
+ ((IProgressMonitorWithBlocking) progressMonitor).clearBlocked();
+ }
+
+ /**
+ * This implementation of a <code>IProgressMonitor</code>
+ * method forwards to the wrapped progress monitor.
+ * Clients may override this method to do additional
+ * processing.
+ *
+ * @see IProgressMonitor#done()
+ */
+ public void done() {
+ progressMonitor.done();
+ }
+
+ /**
+ * Returns the wrapped progress monitor.
+ *
+ * @return the wrapped progress monitor
+ */
+ public IProgressMonitor getWrappedProgressMonitor() {
+ return progressMonitor;
+ }
+
+ /**
+ * This implementation of a <code>IProgressMonitor</code>
+ * method forwards to the wrapped progress monitor.
+ * Clients may override this method to do additional
+ * processing.
+ *
+ * @see IProgressMonitor#internalWorked(double)
+ */
+ public void internalWorked(double work) {
+ progressMonitor.internalWorked(work);
+ }
+
+ /**
+ * This implementation of a <code>IProgressMonitor</code>
+ * method forwards to the wrapped progress monitor.
+ * Clients may override this method to do additional
+ * processing.
+ *
+ * @see IProgressMonitor#isCanceled()
+ */
+ public boolean isCanceled() {
+ return progressMonitor.isCanceled();
+ }
+
+ /**
+ * This implementation of a <code>IProgressMonitorWithBlocking</code>
+ * method forwards to the wrapped progress monitor.
+ * Clients may override this method to do additional
+ * processing.
+ *
+ * @see IProgressMonitorWithBlocking#setBlocked(IStatus)
+ * @since 3.0
+ */
+ public void setBlocked(IStatus reason) {
+ if (progressMonitor instanceof IProgressMonitorWithBlocking)
+ ((IProgressMonitorWithBlocking) progressMonitor).setBlocked(reason);
+ }
+
+ /**
+ * This implementation of a <code>IProgressMonitor</code>
+ * method forwards to the wrapped progress monitor.
+ * Clients may override this method to do additional
+ * processing.
+ *
+ * @see IProgressMonitor#setCanceled(boolean)
+ */
+ public void setCanceled(boolean b) {
+ progressMonitor.setCanceled(b);
+ }
+
+ /**
+ * This implementation of a <code>IProgressMonitor</code>
+ * method forwards to the wrapped progress monitor.
+ * Clients may override this method to do additional
+ * processing.
+ *
+ * @see IProgressMonitor#setTaskName(String)
+ */
+ public void setTaskName(String name) {
+ progressMonitor.setTaskName(name);
+ }
+
+ /**
+ * This implementation of a <code>IProgressMonitor</code>
+ * method forwards to the wrapped progress monitor.
+ * Clients may override this method to do additional
+ * processing.
+ *
+ * @see IProgressMonitor#subTask(String)
+ */
+ public void subTask(String name) {
+ progressMonitor.subTask(name);
+ }
+
+ /**
+ * This implementation of a <code>IProgressMonitor</code>
+ * method forwards to the wrapped progress monitor.
+ * Clients may override this method to do additional
+ * processing.
+ *
+ * @see IProgressMonitor#worked(int)
+ */
+ public void worked(int work) {
+ progressMonitor.worked(work);
+ }
+}
diff --git a/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/QualifiedName.java b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/QualifiedName.java
new file mode 100644
index 0000000..893faa7
--- /dev/null
+++ b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/QualifiedName.java
@@ -0,0 +1,114 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 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.runtime;
+
+/**
+ * Qualified names are two-part names: qualifier and local name.
+ * The qualifier must be in URI form (see RFC2396).
+ * Note however that the qualifier may be <code>null</code> if
+ * the default name space is being used. The empty string is not
+ * a valid local name.
+ * <p>
+ * This class is not intended to be subclassed by clients.
+ * </p>
+ */
+public final class QualifiedName {
+
+ /** Qualifier part (potentially <code>null</code>). */
+ /*package*/
+ String qualifier = null;
+
+ /** Local name part. */
+ /*package*/
+ String localName = null;
+
+ /**
+ * Creates and returns a new qualified name with the given qualifier
+ * and local name. The local name must not be the empty string.
+ * The qualifier may be <code>null</code>.
+ * <p>
+ * Clients may instantiate.
+ * </p>
+ * @param qualifier the qualifier string, or <code>null</code>
+ * @param localName the local name string
+ */
+ public QualifiedName(String qualifier, String localName) {
+ Assert.isLegal(localName != null && localName.length() != 0);
+ this.qualifier = qualifier;
+ this.localName = localName;
+ }
+
+ /**
+ * Returns whether this qualified name is equivalent to the given object.
+ * <p>
+ * Qualified names are equal if and only if they have the same
+ * qualified parts and local parts.
+ * Qualified names are not equal to objects other than qualified names.
+ * </p>
+ *
+ * @param obj the object to compare to
+ * @return <code>true</code> if these are equivalent qualified
+ * names, and <code>false</code> otherwise
+ */
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ }
+ if (!(obj instanceof QualifiedName)) {
+ return false;
+ }
+ QualifiedName qName = (QualifiedName) obj;
+ /* There may or may not be a qualifier */
+ if (qualifier == null && qName.getQualifier() != null) {
+ return false;
+ }
+ if (qualifier != null && !qualifier.equals(qName.getQualifier())) {
+ return false;
+ }
+ return localName.equals(qName.getLocalName());
+ }
+
+ /**
+ * Returns the local part of this name.
+ *
+ * @return the local name string
+ */
+ public String getLocalName() {
+ return localName;
+ }
+
+ /**
+ * Returns the qualifier part for this qualified name, or <code>null</code>
+ * if none.
+ *
+ * @return the qualifier string, or <code>null</code>
+ */
+ public String getQualifier() {
+ return qualifier;
+ }
+
+ /* (Intentionally omitted from javadoc)
+ * Implements the method <code>Object.hashCode</code>.
+ *
+ * Returns the hash code for this qualified name.
+ */
+ public int hashCode() {
+ return (qualifier == null ? 0 : qualifier.hashCode()) + localName.hashCode();
+ }
+
+ /**
+ * Converts this qualified name into a string, suitable for
+ * debug purposes only.
+ */
+ public String toString() {
+ return (getQualifier() == null ? "" : getQualifier() + ':') + getLocalName(); //$NON-NLS-1$
+ }
+}
diff --git a/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/SafeRunner.java b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/SafeRunner.java
new file mode 100644
index 0000000..90f9db3
--- /dev/null
+++ b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/SafeRunner.java
@@ -0,0 +1,67 @@
+/*******************************************************************************
+ * Copyright (c) 2005 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.runtime;
+
+import org.eclipse.core.internal.runtime.*;
+import org.eclipse.osgi.util.NLS;
+
+/**
+ * Runs the given ISafeRunnable in a protected mode: exceptions
+ * thrown in the runnable are logged and passed to the runnable's
+ * exception handler. Such exceptions are not rethrown by this method.
+ *
+ * Note that this class requires presence of OSGi.
+ *
+ * @since org.eclipse.equinox.common 1.0
+ */
+public final class SafeRunner {
+
+ /**
+ * Runs the given runnable in a protected mode. Exceptions
+ * thrown in the runnable are logged and passed to the runnable's
+ * exception handler. Such exceptions are not rethrown by this method.
+ *
+ * @param code the runnable to run
+ */
+ public static void run(ISafeRunnable code) {
+ Assert.isNotNull(code);
+ try {
+ code.run();
+ } catch (Exception e) {
+ handleException(code, e);
+ } catch (LinkageError e) {
+ handleException(code, e);
+ }
+ }
+
+ private static void handleException(ISafeRunnable code, Throwable e) {
+ if (!(e instanceof OperationCanceledException)) {
+ // try to obtain the correct plug-in id for the bundle providing the safe runnable
+ String pluginId = CommonOSGiUtils.getDefault().getBundleId(code);
+ if (pluginId == null)
+ pluginId = IRuntimeConstants.NAME;
+ String message = NLS.bind(CommonMessages.meta_pluginProblems, pluginId);
+ IStatus status;
+ if (e instanceof CoreException) {
+ status = new MultiStatus(pluginId, IRuntimeConstants.PLUGIN_ERROR, message, e);
+ ((MultiStatus) status).merge(((CoreException) e).getStatus());
+ } else {
+ status = new Status(IStatus.ERROR, pluginId, IRuntimeConstants.PLUGIN_ERROR, message, e);
+ }
+ // Make sure user sees the exception: if the log is empty, log the exceptions on stderr
+ if (!RuntimeLog.isEmpty())
+ RuntimeLog.log(status);
+ else
+ e.printStackTrace();
+ }
+ code.handleException(e);
+ }
+}
diff --git a/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/Status.java b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/Status.java
new file mode 100644
index 0000000..70a3642
--- /dev/null
+++ b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/Status.java
@@ -0,0 +1,231 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2005 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.runtime;
+
+import org.eclipse.core.internal.runtime.CommonMessages;
+import org.eclipse.core.internal.runtime.IRuntimeConstants;
+
+/**
+ * A concrete status implementation, suitable either for
+ * instantiating or subclassing.
+ */
+public class Status implements IStatus {
+
+ /**
+ * A standard OK status with an "ok" message.
+ *
+ * @since 3.0
+ */
+ public static final IStatus OK_STATUS = new Status(OK, IRuntimeConstants.PI_RUNTIME, OK, CommonMessages.ok, null);
+ /**
+ * A standard CANCEL status with no message.
+ *
+ * @since 3.0
+ */
+ public static final IStatus CANCEL_STATUS = new Status(CANCEL, IRuntimeConstants.PI_RUNTIME, 1, "", null); //$NON-NLS-1$
+ /**
+ * The severity. One of
+ * <ul>
+ * <li><code>CANCEL</code></li>
+ * <li><code>ERROR</code></li>
+ * <li><code>WARNING</code></li>
+ * <li><code>INFO</code></li>
+ * <li>or <code>OK</code> (0)</li>
+ * </ul>
+ */
+ private int severity = OK;
+
+ /** Unique identifier of plug-in.
+ */
+ private String pluginId;
+
+ /** Plug-in-specific status code.
+ */
+ private int code;
+
+ /** Message, localized to the current locale.
+ */
+ private String message;
+
+ /** Wrapped exception, or <code>null</code> if none.
+ */
+ private Throwable exception = null;
+
+ /** Constant to avoid generating garbage.
+ */
+ private static final IStatus[] theEmptyStatusArray = new IStatus[0];
+
+ /**
+ * Creates a new status object. The created status has no children.
+ *
+ * @param severity the severity; one of <code>OK</code>, <code>ERROR</code>,
+ * <code>INFO</code>, <code>WARNING</code>, or <code>CANCEL</code>
+ * @param pluginId the unique identifier of the relevant plug-in
+ * @param code the plug-in-specific status code, or <code>OK</code>
+ * @param message a human-readable message, localized to the
+ * current locale
+ * @param exception a low-level exception, or <code>null</code> if not
+ * applicable
+ */
+ public Status(int severity, String pluginId, int code, String message, Throwable exception) {
+ setSeverity(severity);
+ setPlugin(pluginId);
+ setCode(code);
+ setMessage(message);
+ setException(exception);
+ }
+
+ /* (Intentionally not javadoc'd)
+ * Implements the corresponding method on <code>IStatus</code>.
+ */
+ public IStatus[] getChildren() {
+ return theEmptyStatusArray;
+ }
+
+ /* (Intentionally not javadoc'd)
+ * Implements the corresponding method on <code>IStatus</code>.
+ */
+ public int getCode() {
+ return code;
+ }
+
+ /* (Intentionally not javadoc'd)
+ * Implements the corresponding method on <code>IStatus</code>.
+ */
+ public Throwable getException() {
+ return exception;
+ }
+
+ /* (Intentionally not javadoc'd)
+ * Implements the corresponding method on <code>IStatus</code>.
+ */
+ public String getMessage() {
+ return message;
+ }
+
+ /* (Intentionally not javadoc'd)
+ * Implements the corresponding method on <code>IStatus</code>.
+ */
+ public String getPlugin() {
+ return pluginId;
+ }
+
+ /* (Intentionally not javadoc'd)
+ * Implements the corresponding method on <code>IStatus</code>.
+ */
+ public int getSeverity() {
+ return severity;
+ }
+
+ /* (Intentionally not javadoc'd)
+ * Implements the corresponding method on <code>IStatus</code>.
+ */
+ public boolean isMultiStatus() {
+ return false;
+ }
+
+ /* (Intentionally not javadoc'd)
+ * Implements the corresponding method on <code>IStatus</code>.
+ */
+ public boolean isOK() {
+ return severity == OK;
+ }
+
+ /* (Intentionally not javadoc'd)
+ * Implements the corresponding method on <code>IStatus</code>.
+ */
+ public boolean matches(int severityMask) {
+ return (severity & severityMask) != 0;
+ }
+
+ /**
+ * Sets the status code.
+ *
+ * @param code the plug-in-specific status code, or <code>OK</code>
+ */
+ protected void setCode(int code) {
+ this.code = code;
+ }
+
+ /**
+ * Sets the exception.
+ *
+ * @param exception a low-level exception, or <code>null</code> if not
+ * applicable
+ */
+ protected void setException(Throwable exception) {
+ this.exception = exception;
+ }
+
+ /**
+ * Sets the message.
+ *
+ * @param message a human-readable message, localized to the
+ * current locale
+ */
+ protected void setMessage(String message) {
+ Assert.isLegal(message != null);
+ this.message = message;
+ }
+
+ /**
+ * Sets the plug-in id.
+ *
+ * @param pluginId the unique identifier of the relevant plug-in
+ */
+ protected void setPlugin(String pluginId) {
+ Assert.isLegal(pluginId != null && pluginId.length() > 0);
+ this.pluginId = pluginId;
+ }
+
+ /**
+ * Sets the severity.
+ *
+ * @param severity the severity; one of <code>OK</code>, <code>ERROR</code>,
+ * <code>INFO</code>, <code>WARNING</code>, or <code>CANCEL</code>
+ */
+ protected void setSeverity(int severity) {
+ Assert.isLegal(severity == OK || severity == ERROR || severity == WARNING || severity == INFO || severity == CANCEL);
+ this.severity = severity;
+ }
+
+ /**
+ * Returns a string representation of the status, suitable
+ * for debugging purposes only.
+ */
+ public String toString() {
+ StringBuffer buf = new StringBuffer();
+ buf.append("Status "); //$NON-NLS-1$
+ if (severity == OK) {
+ buf.append("OK"); //$NON-NLS-1$
+ } else if (severity == ERROR) {
+ buf.append("ERROR"); //$NON-NLS-1$
+ } else if (severity == WARNING) {
+ buf.append("WARNING"); //$NON-NLS-1$
+ } else if (severity == INFO) {
+ buf.append("INFO"); //$NON-NLS-1$
+ } else if (severity == CANCEL) {
+ buf.append("CANCEL"); //$NON-NLS-1$
+ } else {
+ buf.append("severity="); //$NON-NLS-1$
+ buf.append(severity);
+ }
+ buf.append(": "); //$NON-NLS-1$
+ buf.append(pluginId);
+ buf.append(" code="); //$NON-NLS-1$
+ buf.append(code);
+ buf.append(' ');
+ buf.append(message);
+ buf.append(' ');
+ buf.append(exception);
+ return buf.toString();
+ }
+}
diff --git a/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/SubProgressMonitor.java b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/SubProgressMonitor.java
new file mode 100644
index 0000000..4257710
--- /dev/null
+++ b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/SubProgressMonitor.java
@@ -0,0 +1,177 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 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.runtime;
+
+/**
+ * A progress monitor that uses a given amount of work ticks
+ * from a parent monitor. It can be used as follows:
+ * <pre>
+ * try {
+ * pm.beginTask("Main Task", 100);
+ * doSomeWork(pm, 30);
+ * SubProgressMonitor subMonitor= new SubProgressMonitor(pm, 40);
+ * try {
+ * subMonitor.beginTask("", 300);
+ * doSomeWork(subMonitor, 300);
+ * } finally {
+ * subMonitor.done();
+ * }
+ * doSomeWork(pm, 30);
+ * } finally {
+ * pm.done();
+ * }
+ * </pre>
+ * <p>
+ * This class may be instantiated or subclassed by clients.
+ * </p>
+ */
+public class SubProgressMonitor extends ProgressMonitorWrapper {
+
+ /**
+ * Style constant indicating that calls to <code>subTask</code>
+ * should not have any effect.
+ *
+ * @see #SubProgressMonitor(IProgressMonitor,int,int)
+ */
+ public static final int SUPPRESS_SUBTASK_LABEL = 1 << 1;
+ /**
+ * Style constant indicating that the main task label
+ * should be prepended to the subtask label.
+ *
+ * @see #SubProgressMonitor(IProgressMonitor,int,int)
+ */
+ public static final int PREPEND_MAIN_LABEL_TO_SUBTASK = 1 << 2;
+
+ private int parentTicks = 0;
+ private double sentToParent = 0.0;
+ private double scale = 0.0;
+ private int nestedBeginTasks = 0;
+ private boolean usedUp = false;
+ private boolean hasSubTask = false;
+ private int style;
+ private String mainTaskLabel;
+
+ /**
+ * Creates a new sub-progress monitor for the given monitor. The sub
+ * progress monitor uses the given number of work ticks from its
+ * parent monitor.
+ *
+ * @param monitor the parent progress monitor
+ * @param ticks the number of work ticks allocated from the
+ * parent monitor
+ */
+ public SubProgressMonitor(IProgressMonitor monitor, int ticks) {
+ this(monitor, ticks, 0);
+ }
+
+ /**
+ * Creates a new sub-progress monitor for the given monitor. The sub
+ * progress monitor uses the given number of work ticks from its
+ * parent monitor.
+ *
+ * @param monitor the parent progress monitor
+ * @param ticks the number of work ticks allocated from the
+ * parent monitor
+ * @param style one of
+ * <ul>
+ * <li> <code>SUPPRESS_SUBTASK_LABEL</code> </li>
+ * <li> <code>PREPEND_MAIN_LABEL_TO_SUBTASK</code> </li>
+ * </ul>
+ * @see #SUPPRESS_SUBTASK_LABEL
+ * @see #PREPEND_MAIN_LABEL_TO_SUBTASK
+ */
+ public SubProgressMonitor(IProgressMonitor monitor, int ticks, int style) {
+ super(monitor);
+ this.parentTicks = ticks;
+ this.style = style;
+ }
+
+ /* (Intentionally not javadoc'd)
+ * Implements the method <code>IProgressMonitor.beginTask</code>.
+ *
+ * Starts a new main task. Since this progress monitor is a sub
+ * progress monitor, the given name will NOT be used to update
+ * the progress bar's main task label. That means the given
+ * string will be ignored. If style <code>PREPEND_MAIN_LABEL_TO_SUBTASK
+ * <code> is specified, then the given string will be prepended to
+ * every string passed to <code>subTask(String)</code>.
+ */
+ public void beginTask(String name, int totalWork) {
+ nestedBeginTasks++;
+ // Ignore nested begin task calls.
+ if (nestedBeginTasks > 1) {
+ return;
+ }
+ // be safe: if the argument would cause math errors (zero or
+ // negative), just use 0 as the scale. This disables progress for
+ // this submonitor.
+ scale = totalWork <= 0 ? 0 : (double) parentTicks / (double) totalWork;
+ if ((style & PREPEND_MAIN_LABEL_TO_SUBTASK) != 0) {
+ mainTaskLabel = name;
+ }
+ }
+
+ /* (Intentionally not javadoc'd)
+ * Implements the method <code>IProgressMonitor.done</code>.
+ */
+ public void done() {
+ // Ignore if more done calls than beginTask calls or if we are still
+ // in some nested beginTasks
+ if (nestedBeginTasks == 0 || --nestedBeginTasks > 0)
+ return;
+ // Send any remaining ticks and clear out the subtask text
+ double remaining = parentTicks - sentToParent;
+ if (remaining > 0)
+ super.internalWorked(remaining);
+ //clear the sub task if there was one
+ if (hasSubTask)
+ subTask(""); //$NON-NLS-1$
+ sentToParent = 0;
+ }
+
+ /* (Intentionally not javadoc'd)
+ * Implements the internal method <code>IProgressMonitor.internalWorked</code>.
+ */
+ public void internalWorked(double work) {
+ if (usedUp || nestedBeginTasks != 1) {
+ return;
+ }
+
+ double realWork = scale * work;
+ super.internalWorked(realWork);
+ sentToParent += realWork;
+ if (sentToParent >= parentTicks) {
+ usedUp = true;
+ }
+ }
+
+ /* (Intentionally not javadoc'd)
+ * Implements the method <code>IProgressMonitor.subTask</code>.
+ */
+ public void subTask(String name) {
+ if ((style & SUPPRESS_SUBTASK_LABEL) != 0) {
+ return;
+ }
+ hasSubTask = true;
+ String label = name;
+ if ((style & PREPEND_MAIN_LABEL_TO_SUBTASK) != 0 && mainTaskLabel != null && mainTaskLabel.length() > 0) {
+ label = mainTaskLabel + ' ' + label;
+ }
+ super.subTask(label);
+ }
+
+ /* (Intentionally not javadoc'd)
+ * Implements the method <code>IProgressMonitor.worked</code>.
+ */
+ public void worked(int work) {
+ internalWorked(work);
+ }
+}
diff --git a/bundles/org.eclipse.equinox.preferences/.classpath b/bundles/org.eclipse.equinox.preferences/.classpath
new file mode 100644
index 0000000..751c8f2
--- /dev/null
+++ b/bundles/org.eclipse.equinox.preferences/.classpath
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="src" path="src"/>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+ <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/bundles/org.eclipse.equinox.preferences/.cvsignore b/bundles/org.eclipse.equinox.preferences/.cvsignore
new file mode 100644
index 0000000..ba077a4
--- /dev/null
+++ b/bundles/org.eclipse.equinox.preferences/.cvsignore
@@ -0,0 +1 @@
+bin
diff --git a/bundles/org.eclipse.equinox.preferences/.options b/bundles/org.eclipse.equinox.preferences/.options
new file mode 100644
index 0000000..c676c7c
--- /dev/null
+++ b/bundles/org.eclipse.equinox.preferences/.options
@@ -0,0 +1,6 @@
+# Debugging options for the org.eclipse.equinox.preferences plugin
+
+# Turn on debugging for preferences
+org.eclipse.equinox.preferences/general=false
+org.eclipse.equinox.preferences/get=false
+org.eclipse.equinox.preferences/set=false
diff --git a/bundles/org.eclipse.equinox.preferences/.project b/bundles/org.eclipse.equinox.preferences/.project
new file mode 100644
index 0000000..0ccb375
--- /dev/null
+++ b/bundles/org.eclipse.equinox.preferences/.project
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>org.eclipse.equinox.preferences</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.pde.ManifestBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.pde.SchemaBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.pde.PluginNature</nature>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ </natures>
+</projectDescription>
diff --git a/bundles/org.eclipse.equinox.preferences/META-INF/MANIFEST.MF b/bundles/org.eclipse.equinox.preferences/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..ac1f9ed
--- /dev/null
+++ b/bundles/org.eclipse.equinox.preferences/META-INF/MANIFEST.MF
@@ -0,0 +1,16 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: %pluginName
+Bundle-SymbolicName: org.eclipse.equinox.preferences; singleton:=true
+Bundle-Version: 1.0.0.qualifier
+Bundle-Activator: org.eclipse.core.internal.preferences.Activator
+Bundle-Vendor: %providerName
+Bundle-Localization: plugin
+Require-Bundle: org.eclipse.equinox.common,
+ system.bundle
+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"
+Eclipse-LazyStart: true
+Import-Package: org.eclipse.equinox.registry
diff --git a/bundles/org.eclipse.equinox.preferences/about.html b/bundles/org.eclipse.equinox.preferences/about.html
new file mode 100644
index 0000000..bd87495
--- /dev/null
+++ b/bundles/org.eclipse.equinox.preferences/about.html
@@ -0,0 +1,60 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN">
+<html>
+<head>
+<title>About</title>
+<meta http-equiv=Content-Type content="text/html; charset=ISO-8859-1">
+</head>
+<body lang="EN-US">
+<h2>About This Content</h2>
+
+<p>September 26, 2005</p>
+<h3>License</h3>
+
+<p>The Eclipse Foundation makes available all content in this plug-in ("Content"). Unless otherwise indicated below, the Content is provided to you under the terms and conditions of the
+Eclipse Public License Version 1.0 ("EPL"). A copy of the EPL is available at <a href="http://www.eclipse.org/legal/epl-v10.html" target="_blank">http://www.eclipse.org/legal/epl-v10.html</a>.
+For purposes of the EPL, "Program" will mean the Content.</p>
+
+<p>If you did not receive this Content directly from the Eclipse Foundation, the Content is being redistributed by another party ("Redistributor") and different terms and conditions may
+apply to your use of any object code in the Content. Check the Redistributor's license that was provided with the Content. If no such license exists, contact the Redistributor. Unless otherwise
+indicated below, the terms and conditions of the EPL still apply to any source code in the Content.</p>
+
+<h3>Third Party Content</h3>
+
+<p>The Content includes items that have been sourced from third parties as follows:</p>
+
+<p><b>OSGi Materials</b></p>
+
+<p>All files in the following sub-directories (and their sub-directories):</p>
+
+<ul>
+ <li>org/osgi</li>
+</ul>
+
+<p>shall be defined as the "OSGi Materials." The OSGi Materials are:</p>
+
+<blockquote>
+Copyright (c) 2000, 2005
+<br /><br />
+OSGi Alliance
+Bishop Ranch 6<br/>
+2400 Camino Ramon, Suite 375<br/>
+San Ramon, CA 94583 USA
+<br /><br />
+All Rights Reserved.
+</blockquote>
+
+<p>The OSGi Materials are provided to you under the terms and conditions of the
+Eclipse Public License Version 1.0 ("EPL"). For purposes of the EPL, "Program" will mean the OSGi Materials.</p>
+
+<p>Implementation of certain elements of the OSGi Materials may be subject to third party intellectual property rights, including without limitation, patent rights (such a third party may
+or may not be a member of the OSGi Alliance). The OSGi Alliance and its members are not responsible and shall not be held responsible in any manner for identifying or failing to identify any or all such third party
+intellectual property rights.</p>
+
+<small>OSGi™ is a trademark, registered trademark, or service mark of The OSGi Alliance in the US and other countries. Java is a trademark,
+registered trademark, or service mark of Sun Microsystems, Inc. in the US and other countries. All other trademarks, registered trademarks, or
+service marks used in the Content are the property of their respective owners and are hereby recognized.</small>
+
+<small>Java and all Java-based trademarks and logos are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries.</small>
+
+</body>
+</html>
diff --git a/bundles/org.eclipse.equinox.preferences/build.properties b/bundles/org.eclipse.equinox.preferences/build.properties
new file mode 100644
index 0000000..b3000cc
--- /dev/null
+++ b/bundles/org.eclipse.equinox.preferences/build.properties
@@ -0,0 +1,19 @@
+###############################################################################
+# Copyright (c) 2005 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
+###############################################################################
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+ .,\
+ plugin.xml,\
+ plugin.properties,\
+ .options,\
+ about.html
+src.includes = about.html
diff --git a/bundles/org.eclipse.equinox.preferences/plugin.properties b/bundles/org.eclipse.equinox.preferences/plugin.properties
new file mode 100644
index 0000000..85acdaa
--- /dev/null
+++ b/bundles/org.eclipse.equinox.preferences/plugin.properties
@@ -0,0 +1,14 @@
+###############################################################################
+# Copyright (c) 2005 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
+###############################################################################
+pluginName = Eclipse Preferences Mechanism
+providerName = Eclipse.org
+preferencesExtPtName = Platform Preferences
+preferencesName=Preferences
diff --git a/bundles/org.eclipse.equinox.preferences/plugin.xml b/bundles/org.eclipse.equinox.preferences/plugin.xml
new file mode 100644
index 0000000..cfaffa8
--- /dev/null
+++ b/bundles/org.eclipse.equinox.preferences/plugin.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<?eclipse version="3.0"?>
+<plugin>
+ <extension-point id="preferences" name="%preferencesName" schema="schema/preferences.exsd"/>
+
+ <extension id="org.eclipse.core.runtime.platform-preferences" point="org.eclipse.equinox.preferences.preferences" name="%preferencesExtPtName">
+ <scope name="configuration" class="org.eclipse.core.internal.preferences.ConfigurationPreferences"/>
+ <scope name="instance" class="org.eclipse.core.internal.preferences.InstancePreferences"/>
+ <scope name="default" class="org.eclipse.core.internal.preferences.DefaultPreferences"/>
+ </extension>
+</plugin>
diff --git a/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/AbstractScope.java b/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/AbstractScope.java
new file mode 100644
index 0000000..6ffebd0
--- /dev/null
+++ b/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/AbstractScope.java
@@ -0,0 +1,67 @@
+/*******************************************************************************
+ * 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 org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.preferences.IEclipsePreferences;
+import org.eclipse.core.runtime.preferences.IScopeContext;
+
+/**
+ * Abstract super-class for scope context object contributed
+ * by the Platform.
+ *
+ * @since 3.0
+ */
+public abstract class AbstractScope implements IScopeContext {
+
+ /*
+ * @see org.eclipse.core.runtime.preferences.IScopeContext#getName()
+ */
+ public abstract String getName();
+
+ /*
+ * Default path hierarchy for nodes is /<scope>/<qualifier>.
+ *
+ * @see org.eclipse.core.runtime.preferences.IScopeContext#getNode(java.lang.String)
+ */
+ public IEclipsePreferences getNode(String qualifier) {
+ if (qualifier == null)
+ throw new IllegalArgumentException();
+ return (IEclipsePreferences) PreferencesService.getDefault().getRootNode().node(getName()).node(qualifier);
+ }
+
+ /*
+ * @see org.eclipse.core.runtime.preferences.IScopeContext#getLocation()
+ */
+ public abstract IPath getLocation();
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (!(obj instanceof IScopeContext))
+ return false;
+ IScopeContext other = (IScopeContext) obj;
+ if (!getName().equals(other.getName()))
+ return false;
+ IPath location = getLocation();
+ return location == null ? other.getLocation() == null : location.equals(other.getLocation());
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#hashCode()
+ */
+ public int hashCode() {
+ return getName().hashCode();
+ }
+}
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
new file mode 100644
index 0000000..1735ddd
--- /dev/null
+++ b/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/Activator.java
@@ -0,0 +1,88 @@
+/*******************************************************************************
+ * Copyright (c) 2005 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.util.Hashtable;
+import org.eclipse.core.runtime.preferences.IPreferencesService;
+import org.eclipse.osgi.service.environment.EnvironmentInfo;
+import org.osgi.framework.*;
+import org.osgi.util.tracker.ServiceTracker;
+
+/**
+ * The Jobs plugin class.
+ */
+public class Activator implements BundleActivator {
+
+ /**
+ * The bundle associated this plug-in
+ */
+ private static BundleContext bundleContext;
+
+ /**
+ * This plugin provides a Preferences service.
+ */
+ private ServiceRegistration preferencesService = null;
+
+ /**
+ * This method is called upon plug-in activation
+ */
+ public void start(BundleContext context) throws Exception {
+ bundleContext = context;
+ processCommandLine();
+ registerServices();
+ }
+
+ /**
+ * This method is called when the plug-in is stopped
+ */
+ public void stop(BundleContext context) throws Exception {
+ unregisterServices();
+ PreferencesOSGiUtils.getDefault().closeServices();
+ bundleContext = null;
+ }
+
+ static BundleContext getContext() {
+ return bundleContext;
+ }
+
+ private void registerServices() {
+ preferencesService = bundleContext.registerService(IPreferencesService.class.getName(), PreferencesService.getDefault(), new Hashtable());
+ }
+
+ private void unregisterServices() {
+ preferencesService.unregister();
+ }
+
+ /**
+ * Look for the plug-in customization file.
+ * @param args - command line arguments
+ */
+ private void processCommandLine() {
+ ServiceTracker environmentTracker = new ServiceTracker(bundleContext, EnvironmentInfo.class.getName(), null);
+ environmentTracker.open();
+ EnvironmentInfo environmentInfo = (EnvironmentInfo) environmentTracker.getService();
+ environmentTracker.close();
+ if (environmentInfo == null)
+ return;
+ String[] args = environmentInfo.getNonFrameworkArgs();
+ if (args == null || args.length == 0)
+ return;
+
+ for (int i = 0; i < args.length; i++) {
+ if (args[i].equalsIgnoreCase(IPreferencesConstants.PLUGIN_CUSTOMIZATION)) {
+ if (args.length > i + 1) // make sure the file name is actually there
+ DefaultPreferences.pluginCustomizationFile = args[i + 1];
+ break; // only interested in this one
+ }
+ }
+ }
+
+}
diff --git a/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/Base64.java b/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/Base64.java
new file mode 100644
index 0000000..de85184
--- /dev/null
+++ b/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/Base64.java
@@ -0,0 +1,198 @@
+/*******************************************************************************
+ * 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;
+
+public class Base64 {
+
+ private static final byte equalSign = (byte) '=';
+
+ static char digits[] = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', //
+ 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', //
+ 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', //
+ 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'};
+
+ /**
+ * This method decodes the byte array in base 64 encoding into a char array
+ * Base 64 encoding has to be according to the specification given by the
+ * RFC 1521 (5.2).
+ *
+ * @param data the encoded byte array
+ * @return the decoded byte array
+ */
+ public static byte[] decode(byte[] data) {
+ if (data.length == 0)
+ return data;
+ int lastRealDataIndex = data.length - 1;
+ while (data[lastRealDataIndex] == equalSign)
+ lastRealDataIndex--;
+ // original data digit is 8 bits long, but base64 digit is 6 bits long
+ int padBytes = data.length - 1 - lastRealDataIndex;
+ int byteLength = data.length * 6 / 8 - padBytes;
+ byte[] result = new byte[byteLength];
+ // Each 4 bytes of input (encoded) we end up with 3 bytes of output
+ int dataIndex = 0;
+ int resultIndex = 0;
+ int allBits = 0;
+ // how many result chunks we can process before getting to pad bytes
+ int resultChunks = (lastRealDataIndex + 1) / 4;
+ for (int i = 0; i < resultChunks; i++) {
+ allBits = 0;
+ // Loop 4 times gathering input bits (4 * 6 = 24)
+ for (int j = 0; j < 4; j++)
+ allBits = (allBits << 6) | decodeDigit(data[dataIndex++]);
+ // Loop 3 times generating output bits (3 * 8 = 24)
+ for (int j = resultIndex + 2; j >= resultIndex; j--) {
+ result[j] = (byte) (allBits & 0xff); // Bottom 8 bits
+ allBits = allBits >>> 8;
+ }
+ resultIndex += 3; // processed 3 result bytes
+ }
+ // Now we do the extra bytes in case the original (non-encoded) data
+ // was not multiple of 3 bytes
+ switch (padBytes) {
+ case 1 :
+ // 1 pad byte means 3 (4-1) extra Base64 bytes of input, 18
+ // bits, of which only 16 are meaningful
+ // Or: 2 bytes of result data
+ allBits = 0;
+ // Loop 3 times gathering input bits
+ for (int j = 0; j < 3; j++)
+ allBits = (allBits << 6) | decodeDigit(data[dataIndex++]);
+ // NOTE - The code below ends up being equivalent to allBits =
+ // allBits>>>2
+ // But we code it in a non-optimized way for clarity
+ // The 4th, missing 6 bits are all 0
+ allBits = allBits << 6;
+ // The 3rd, missing 8 bits are all 0
+ allBits = allBits >>> 8;
+ // Loop 2 times generating output bits
+ for (int j = resultIndex + 1; j >= resultIndex; j--) {
+ result[j] = (byte) (allBits & 0xff); // Bottom 8
+ // bits
+ allBits = allBits >>> 8;
+ }
+ break;
+ case 2 :
+ // 2 pad bytes mean 2 (4-2) extra Base64 bytes of input, 12 bits
+ // of data, of which only 8 are meaningful
+ // Or: 1 byte of result data
+ allBits = 0;
+ // Loop 2 times gathering input bits
+ for (int j = 0; j < 2; j++)
+ allBits = (allBits << 6) | decodeDigit(data[dataIndex++]);
+ // NOTE - The code below ends up being equivalent to allBits =
+ // allBits>>>4
+ // But we code it in a non-optimized way for clarity
+ // The 3rd and 4th, missing 6 bits are all 0
+ allBits = allBits << 6;
+ allBits = allBits << 6;
+ // The 3rd and 4th, missing 8 bits are all 0
+ allBits = allBits >>> 8;
+ allBits = allBits >>> 8;
+ result[resultIndex] = (byte) (allBits & 0xff); // Bottom
+ // 8
+ // bits
+ break;
+ }
+ return result;
+ }
+
+ /**
+ * This method converts a Base 64 digit to its numeric value.
+ *
+ * @param data digit (character) to convert
+ * @return value for the digit
+ */
+ static int decodeDigit(byte data) {
+ char charData = (char) data;
+ if (charData <= 'Z' && charData >= 'A')
+ return charData - 'A';
+ if (charData <= 'z' && charData >= 'a')
+ return charData - 'a' + 26;
+ if (charData <= '9' && charData >= '0')
+ return charData - '0' + 52;
+ switch (charData) {
+ case '+' :
+ return 62;
+ case '/' :
+ return 63;
+ default :
+ throw new IllegalArgumentException("Invalid char to decode: " + data); //$NON-NLS-1$
+ }
+ }
+
+ /**
+ * This method encodes the byte array into a char array in base 64 according
+ * to the specification given by the RFC 1521 (5.2).
+ *
+ * @param data the encoded char array
+ * @return the byte array that needs to be encoded
+ */
+ public static byte[] encode(byte[] data) {
+ int sourceChunks = data.length / 3;
+ int len = ((data.length + 2) / 3) * 4;
+ byte[] result = new byte[len];
+ int extraBytes = data.length - (sourceChunks * 3);
+ // Each 4 bytes of input (encoded) we end up with 3 bytes of output
+ int dataIndex = 0;
+ int resultIndex = 0;
+ int allBits = 0;
+ for (int i = 0; i < sourceChunks; i++) {
+ allBits = 0;
+ // Loop 3 times gathering input bits (3 * 8 = 24)
+ for (int j = 0; j < 3; j++)
+ allBits = (allBits << 8) | (data[dataIndex++] & 0xff);
+ // Loop 4 times generating output bits (4 * 6 = 24)
+ for (int j = resultIndex + 3; j >= resultIndex; j--) {
+ result[j] = (byte) digits[(allBits & 0x3f)]; // Bottom
+ // 6
+ // bits
+ allBits = allBits >>> 6;
+ }
+ resultIndex += 4; // processed 4 result bytes
+ }
+ // Now we do the extra bytes in case the original (non-encoded) data
+ // is not multiple of 4 bytes
+ switch (extraBytes) {
+ case 1 :
+ allBits = data[dataIndex++]; // actual byte
+ allBits = allBits << 8; // 8 bits of zeroes
+ allBits = allBits << 8; // 8 bits of zeroes
+ // Loop 4 times generating output bits (4 * 6 = 24)
+ for (int j = resultIndex + 3; j >= resultIndex; j--) {
+ result[j] = (byte) digits[(allBits & 0x3f)]; // Bottom
+ // 6
+ // bits
+ allBits = allBits >>> 6;
+ }
+ // 2 pad tags
+ result[result.length - 1] = (byte) '=';
+ result[result.length - 2] = (byte) '=';
+ break;
+ case 2 :
+ allBits = data[dataIndex++]; // actual byte
+ allBits = (allBits << 8) | (data[dataIndex++] & 0xff); // actual
+ // byte
+ allBits = allBits << 8; // 8 bits of zeroes
+ // Loop 4 times generating output bits (4 * 6 = 24)
+ for (int j = resultIndex + 3; j >= resultIndex; j--) {
+ result[j] = (byte) digits[(allBits & 0x3f)]; // Bottom
+ // 6
+ // bits
+ allBits = allBits >>> 6;
+ }
+ // 1 pad tag
+ result[result.length - 1] = (byte) '=';
+ break;
+ }
+ return result;
+ }
+}
diff --git a/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/ConfigurationPreferences.java b/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/ConfigurationPreferences.java
new file mode 100644
index 0000000..72e1426
--- /dev/null
+++ b/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/ConfigurationPreferences.java
@@ -0,0 +1,118 @@
+/*******************************************************************************
+ * 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.net.URL;
+import java.util.HashSet;
+import java.util.Set;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.core.runtime.preferences.IEclipsePreferences;
+
+/**
+ * @since 3.0
+ */
+public class ConfigurationPreferences extends EclipsePreferences {
+
+ // cached values
+ private int segmentCount;
+ private String qualifier;
+ private IPath location;
+ private IEclipsePreferences loadLevel;
+ // cache which nodes have been loaded from disk
+ private static Set loadedNodes = new HashSet();
+ private static boolean initialized = false;
+ private static IPath baseLocation;
+
+ static {
+ URL url = PreferencesOSGiUtils.getDefault().getConfigurationLocation().getURL();
+ if (url != null)
+ baseLocation = new Path(url.getFile());
+ }
+
+ /**
+ * Default constructor. Should only be called by #createExecutableExtension.
+ */
+ public ConfigurationPreferences() {
+ this(null, null);
+ }
+
+ private ConfigurationPreferences(EclipsePreferences parent, String name) {
+ super(parent, name);
+
+ initializeChildren();
+
+ // cache the segment count
+ String path = absolutePath();
+ segmentCount = getSegmentCount(path);
+ if (segmentCount < 2)
+ return;
+
+ // cache the qualifier
+ qualifier = getSegment(path, 1);
+
+ // cache the location
+ if (qualifier == null)
+ return;
+ if (baseLocation != null)
+ location = computeLocation(baseLocation, qualifier);
+ }
+
+ protected IPath getLocation() {
+ return location;
+ }
+
+ protected boolean isAlreadyLoaded(IEclipsePreferences node) {
+ return loadedNodes.contains(node.name());
+ }
+
+ protected void loaded() {
+ loadedNodes.add(name());
+ }
+
+ /*
+ * Return the node at which these preferences are loaded/saved.
+ */
+ protected IEclipsePreferences getLoadLevel() {
+ if (loadLevel == null) {
+ if (qualifier == null)
+ return null;
+ // Make it relative to this node rather than navigating to it from the root.
+ // Walk backwards up the tree starting at this node.
+ // This is important to avoid a chicken/egg thing on startup.
+ IEclipsePreferences node = this;
+ for (int i = 2; i < segmentCount; i++)
+ node = (EclipsePreferences) node.parent();
+ loadLevel = node;
+ }
+ return loadLevel;
+ }
+
+ protected void initializeChildren() {
+ if (initialized || parent == null)
+ return;
+ try {
+ synchronized (this) {
+ if (baseLocation == null)
+ return;
+ String[] names = computeChildren(baseLocation);
+ for (int i = 0; i < names.length; i++)
+ addChild(names[i], null);
+ }
+ } finally {
+ initialized = true;
+ }
+ }
+
+ protected EclipsePreferences internalCreate(EclipsePreferences nodeParent, String nodeName, Object context) {
+ return new ConfigurationPreferences(nodeParent, nodeName);
+ }
+}
diff --git a/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/DefaultPreferences.java b/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/DefaultPreferences.java
new file mode 100644
index 0000000..cacf87c
--- /dev/null
+++ b/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/DefaultPreferences.java
@@ -0,0 +1,360 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2005 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.*;
+import java.net.URL;
+import java.util.*;
+import org.eclipse.core.internal.runtime.RuntimeLog;
+import org.eclipse.core.runtime.*;
+import org.eclipse.core.runtime.preferences.*;
+import org.eclipse.equinox.registry.IConfigurationElement;
+import org.eclipse.equinox.registry.IExtension;
+import org.eclipse.osgi.util.NLS;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.util.tracker.ServiceTracker;
+
+/**
+ * @since 3.0
+ */
+public class DefaultPreferences extends EclipsePreferences {
+ // cache which nodes have been loaded from disk
+ private static Set loadedNodes = new HashSet();
+ private static final String ELEMENT_INITIALIZER = "initializer"; //$NON-NLS-1$
+ private static final String ATTRIBUTE_CLASS = "class"; //$NON-NLS-1$
+ private static final String KEY_PREFIX = "%"; //$NON-NLS-1$
+ private static final String KEY_DOUBLE_PREFIX = "%%"; //$NON-NLS-1$
+ private static final IPath NL_DIR = new Path("$nl$"); //$NON-NLS-1$
+
+ private static final String PROPERTIES_FILE_EXTENSION = "properties"; //$NON-NLS-1$
+ private static Properties productCustomization;
+ private static Properties productTranslation;
+ private static Properties commandLineCustomization;
+ private EclipsePreferences loadLevel;
+
+ // cached values
+ private String qualifier;
+ private int segmentCount;
+ private Object plugin;
+
+ public static String pluginCustomizationFile = null;
+
+ /**
+ * Default constructor for this class.
+ */
+ public DefaultPreferences() {
+ this(null, null);
+ }
+
+ private DefaultPreferences(EclipsePreferences parent, String name, Object context) {
+ this(parent, name);
+ this.plugin = context;
+ }
+
+ private DefaultPreferences(EclipsePreferences parent, String name) {
+ super(parent, name);
+
+ if (parent instanceof DefaultPreferences)
+ this.plugin = ((DefaultPreferences) parent).plugin;
+
+ // cache the segment count
+ String path = absolutePath();
+ segmentCount = getSegmentCount(path);
+ if (segmentCount < 2)
+ return;
+
+ // cache the qualifier
+ qualifier = getSegment(path, 1);
+ }
+
+ /*
+ * Apply the values set in the bundle's install directory.
+ *
+ * In Eclipse 2.1 this is equivalent to:
+ * /eclipse/plugins/<pluginID>/prefs.ini
+ */
+ private void applyBundleDefaults() {
+ Bundle bundle = PreferencesOSGiUtils.getDefault().getBundle(name());
+ if (bundle == null)
+ return;
+ URL url = BundleFinder.find(bundle, new Path(Preferences.PREFERENCES_DEFAULT_OVERRIDE_FILE_NAME));
+ if (url == null) {
+ if (EclipsePreferences.DEBUG_PREFERENCE_GENERAL)
+ PrefsMessages.message("Preference default override file not found for bundle: " + bundle.getSymbolicName()); //$NON-NLS-1$
+ return;
+ }
+ URL transURL = BundleFinder.find(bundle, NL_DIR.append(Preferences.PREFERENCES_DEFAULT_OVERRIDE_BASE_NAME).addFileExtension(PROPERTIES_FILE_EXTENSION));
+ if (transURL == null && EclipsePreferences.DEBUG_PREFERENCE_GENERAL)
+ PrefsMessages.message("Preference translation file not found for bundle: " + bundle.getSymbolicName()); //$NON-NLS-1$
+ applyDefaults(name(), loadProperties(url), loadProperties(transURL));
+ }
+
+ /*
+ * Apply the default values as specified in the file
+ * as an argument on the command-line.
+ */
+ private void applyCommandLineDefaults() {
+ // prime the cache the first time
+ if (commandLineCustomization == null) {
+ String filename = pluginCustomizationFile;
+ if (filename == null) {
+ if (EclipsePreferences.DEBUG_PREFERENCE_GENERAL)
+ PrefsMessages.message("Command-line preferences customization file not specified."); //$NON-NLS-1$
+ return;
+ }
+ if (EclipsePreferences.DEBUG_PREFERENCE_GENERAL)
+ PrefsMessages.message("Using command-line preference customization file: " + filename); //$NON-NLS-1$
+ commandLineCustomization = loadProperties(filename);
+ }
+ applyDefaults(null, commandLineCustomization, null);
+ }
+
+ /*
+ * If the qualifier is null then the file is of the format:
+ * pluginID/key=value
+ * otherwise the file is of the format:
+ * key=value
+ */
+ private void applyDefaults(String id, Properties defaultValues, Properties translations) {
+ for (Enumeration e = defaultValues.keys(); e.hasMoreElements();) {
+ String fullKey = (String) e.nextElement();
+ String value = defaultValues.getProperty(fullKey);
+ if (value == null)
+ continue;
+ IPath childPath = new Path(fullKey);
+ String key = childPath.lastSegment();
+ childPath = childPath.removeLastSegments(1);
+ String localQualifier = id;
+ if (id == null) {
+ localQualifier = childPath.segment(0);
+ childPath = childPath.removeFirstSegments(1);
+ }
+ if (name().equals(localQualifier)) {
+ value = translatePreference(value, translations);
+ if (EclipsePreferences.DEBUG_PREFERENCE_SET)
+ PrefsMessages.message("Setting default preference: " + (new Path(absolutePath()).append(childPath).append(key)) + '=' + value); //$NON-NLS-1$
+ ((EclipsePreferences) internalNode(childPath.toString(), false, null)).internalPut(key, value);
+ }
+ }
+ }
+
+ private void runInitializer(IConfigurationElement element) {
+ AbstractPreferenceInitializer initializer = null;
+ try {
+ initializer = (AbstractPreferenceInitializer) element.createExecutableExtension(ATTRIBUTE_CLASS);
+ initializer.initializeDefaultPreferences();
+ } catch (ClassCastException e) {
+ IStatus status = new Status(IStatus.ERROR, PrefsMessages.OWNER_NAME, IStatus.ERROR, PrefsMessages.preferences_invalidExtensionSuperclass, e);
+ log(status);
+ } catch (CoreException e) {
+ log(e.getStatus());
+ }
+ }
+
+ public IEclipsePreferences node(String childName, Object context) {
+ return internalNode(childName, true, context);
+ }
+
+ /*
+ * Runtime defaults are the ones which are specified in code at runtime.
+ *
+ * In the Eclipse 2.1 world they were the ones which were specified in the
+ * over-ridden Plugin#initializeDefaultPluginPreferences() method.
+ *
+ * In Eclipse 3.0 they are set in the code which is indicated by the
+ * extension to the plug-in default customizer extension point.
+ */
+ private void applyRuntimeDefaults() {
+ IExtension[] extensions = PreferencesService.getPrefExtensions();
+ if (extensions.length == 0) {
+ if (EclipsePreferences.DEBUG_PREFERENCE_GENERAL)
+ PrefsMessages.message("Skipping runtime default preference customization."); //$NON-NLS-1$
+ return;
+ }
+ boolean foundInitializer = false;
+ for (int i = 0; i < extensions.length; i++) {
+ IConfigurationElement[] elements = extensions[i].getConfigurationElements();
+ for (int j = 0; j < elements.length; j++)
+ if (ELEMENT_INITIALIZER.equals(elements[j].getName())) {
+ if (name().equals(elements[j].getNamespace())) {
+ if (EclipsePreferences.DEBUG_PREFERENCE_GENERAL) {
+ IExtension theExtension = elements[j].getDeclaringExtension();
+ String extensionNamespace = theExtension.getNamespace();
+ Bundle underlyingBundle = PreferencesOSGiUtils.getDefault().getBundle(extensionNamespace);
+ String ownerName;
+ if (underlyingBundle != null)
+ ownerName = underlyingBundle.getSymbolicName();
+ else
+ ownerName = extensionNamespace;
+ PrefsMessages.message("Running default preference customization as defined by: " + ownerName); //$NON-NLS-1$
+ }
+ runInitializer(elements[j]);
+ // don't return yet in case we have multiple initializers registered
+ foundInitializer = true;
+ }
+ }
+ }
+ if (foundInitializer)
+ return;
+
+ // Do legacy plugin preference initialization
+ ILegacyPreferences initService = PreferencesOSGiUtils.getDefault().getLegacyPreferences();
+ if (initService != null)
+ initService.init(plugin, name());
+ }
+
+ /*
+ * Apply the default values as specified by the file
+ * in the product extension.
+ *
+ * In Eclipse 2.1 this is equivalent to the plugin_customization.ini
+ * file in the primary feature's plug-in directory.
+ */
+ private void applyProductDefaults() {
+ // prime the cache the first time
+ if (productCustomization == null) {
+ BundleContext context = Activator.getContext();
+ if (context != null) {
+ ServiceTracker productTracker = new ServiceTracker(context, IProductPreferencesService.class.getName(), null);
+ productTracker.open();
+ IProductPreferencesService productSpecials = (IProductPreferencesService) productTracker.getService();
+ if (productSpecials != null) {
+ productCustomization = productSpecials.getProductCustomization();
+ productTranslation = productSpecials.getProductTranslation();
+ }
+ productTracker.close();
+ } else
+ PrefsMessages.message("Product-specified preferences called before plugin is started"); //$NON-NLS-1$
+ }
+ applyDefaults(null, productCustomization, productTranslation);
+ }
+
+ /* (non-Javadoc)
+ * @see org.osgi.service.prefs.Preferences#flush()
+ */
+ public void flush() {
+ // default values are not persisted
+ }
+
+ protected IEclipsePreferences getLoadLevel() {
+ if (loadLevel == null) {
+ if (qualifier == null)
+ return null;
+ // Make it relative to this node rather than navigating to it from the root.
+ // Walk backwards up the tree starting at this node.
+ // This is important to avoid a chicken/egg thing on startup.
+ EclipsePreferences node = this;
+ for (int i = 2; i < segmentCount; i++)
+ node = (EclipsePreferences) node.parent();
+ loadLevel = node;
+ }
+ return loadLevel;
+ }
+
+ protected EclipsePreferences internalCreate(EclipsePreferences nodeParent, String nodeName, Object context) {
+ return new DefaultPreferences(nodeParent, nodeName, context);
+ }
+
+ protected boolean isAlreadyLoaded(IEclipsePreferences node) {
+ return loadedNodes.contains(node.name());
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.core.internal.preferences.EclipsePreferences#load()
+ */
+ protected void load() {
+ loadDefaults();
+ }
+
+ private void loadDefaults() {
+ applyRuntimeDefaults();
+ applyBundleDefaults();
+ applyProductDefaults();
+ applyCommandLineDefaults();
+ }
+
+ private Properties loadProperties(URL url) {
+ Properties result = new Properties();
+ if (url == null)
+ return result;
+ InputStream input = null;
+ try {
+ input = url.openStream();
+ result.load(input);
+ } catch (IOException e) {
+ if (EclipsePreferences.DEBUG_PREFERENCE_GENERAL) {
+ PrefsMessages.message("Problem opening stream to preference customization file: " + url); //$NON-NLS-1$
+ e.printStackTrace();
+ }
+ } finally {
+ if (input != null)
+ try {
+ input.close();
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+ return result;
+ }
+
+ private Properties loadProperties(String filename) {
+ Properties result = new Properties();
+ InputStream input = null;
+ try {
+ input = new BufferedInputStream(new FileInputStream(filename));
+ result.load(input);
+ } catch (FileNotFoundException e) {
+ if (EclipsePreferences.DEBUG_PREFERENCE_GENERAL)
+ PrefsMessages.message("Preference customization file not found: " + filename); //$NON-NLS-1$
+ } catch (IOException e) {
+ String message = NLS.bind(PrefsMessages.preferences_loadException, filename);
+ IStatus status = new Status(IStatus.ERROR, PrefsMessages.OWNER_NAME, IStatus.ERROR, message, e);
+ RuntimeLog.log(status);
+ } finally {
+ if (input != null)
+ try {
+ input.close();
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+ return result;
+ }
+
+ protected void loaded() {
+ loadedNodes.add(name());
+ }
+
+ /* (non-Javadoc)
+ * @see org.osgi.service.prefs.Preferences#sync()
+ */
+ public void sync() {
+ // default values are not persisted
+ }
+
+ /**
+ * Takes a preference value and a related resource bundle and
+ * returns the translated version of this value (if one exists).
+ */
+ private String translatePreference(String value, Properties props) {
+ value = value.trim();
+ if (props == null || value.startsWith(KEY_DOUBLE_PREFIX))
+ return value;
+ if (value.startsWith(KEY_PREFIX)) {
+ int ix = value.indexOf(" "); //$NON-NLS-1$
+ String key = ix == -1 ? value.substring(1) : value.substring(1, ix);
+ String dflt = ix == -1 ? value : value.substring(ix + 1);
+ return props.getProperty(key, dflt);
+ }
+ return value;
+ }
+}
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
new file mode 100644
index 0000000..b7b4169
--- /dev/null
+++ b/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/EclipsePreferences.java
@@ -0,0 +1,1163 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2005 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$
+ }
+
+ public EclipsePreferences() {
+ this(null, null);
+ }
+
+ protected EclipsePreferences(EclipsePreferences parent, String name) {
+ super();
+ this.parent = parent;
+ this.name = name;
+ }
+
+ /*
+ * @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))
+ node.firePreferenceEvent(key, oldValue, value);
+ }
+ }
+ PreferencesService.getDefault().shareStrings();
+ }
+
+ /*
+ * 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()
+ */
+ 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();
+ }
+
+ 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();
+ }
+ }
+
+ /*
+ * @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) {
+ 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$
+ }
+
+ 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 Properties(), 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);
+ OutputStream output = null;
+ FileOutputStream fos = 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.
+ fos = new FileOutputStream(location.toOSString(), false);
+ output = new BufferedOutputStream(fos);
+ table.store(output, null);
+ output.flush();
+ fos.getFD().sync();
+ } 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
+ }
+ }
+ }
+
+ /**
+ * 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();
+ }
+}
diff --git a/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/ExportedPreferences.java b/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/ExportedPreferences.java
new file mode 100644
index 0000000..38d6f26
--- /dev/null
+++ b/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/ExportedPreferences.java
@@ -0,0 +1,78 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2005 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 org.eclipse.core.runtime.preferences.IExportedPreferences;
+
+/**
+ * @since 3.0
+ */
+public class ExportedPreferences extends EclipsePreferences implements IExportedPreferences {
+
+ private boolean isExportRoot = false;
+ private String version;
+
+ public static IExportedPreferences newRoot() {
+ return new ExportedPreferences(null, ""); //$NON-NLS-1$
+ }
+
+ protected ExportedPreferences(EclipsePreferences parent, String name) {
+ super(parent, name);
+ }
+
+ /*
+ * @see org.eclipse.core.runtime.preferences.IExportedPreferences#isExportRoot()
+ */
+ public boolean isExportRoot() {
+ return isExportRoot;
+ }
+
+ /*
+ * Internal method called only by the import/export mechanism.
+ */
+ public void setExportRoot() {
+ isExportRoot = true;
+ }
+
+ /*
+ * Internal method called only by the import/export mechanism to
+ * validate bundle versions.
+ */
+ public String getVersion() {
+ return version;
+ }
+
+ /*
+ * Internal method called only by the import/export mechanism to
+ * validate bundle versions.
+ */
+ public void setVersion(String version) {
+ this.version = version;
+ }
+
+ protected EclipsePreferences internalCreate(EclipsePreferences nodeParent, String nodeName, Object context) {
+ return new ExportedPreferences(nodeParent, nodeName);
+ }
+
+ /*
+ * Return a string representation of this object. To be used for
+ * debugging purposes only.
+ */
+ public String toString() {
+ StringBuffer buffer = new StringBuffer();
+ if (isExportRoot)
+ buffer.append("* "); //$NON-NLS-1$
+ buffer.append(absolutePath());
+ if (version != null)
+ buffer.append(" (" + version + ')'); //$NON-NLS-1$
+ return buffer.toString();
+ }
+}
diff --git a/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/ILegacyPreferences.java b/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/ILegacyPreferences.java
new file mode 100644
index 0000000..7c03e18
--- /dev/null
+++ b/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/ILegacyPreferences.java
@@ -0,0 +1,29 @@
+/*******************************************************************************
+ * Copyright (c) 2005 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;
+
+/**
+ * Provides initialization of the legacy preferences as described in
+ * the Plugin class.
+ *
+ * @depreceated
+ */
+public interface ILegacyPreferences {
+ /**
+ * The method tries to initialize the preferences using the legacy Plugin method.
+ *
+ * @param object - plugin to initialize
+ * @param name - ID of the plugin to be initialized
+ *
+ * @see Plugin#initializeDefaultPluginPreferences
+ */
+ public void init(Object object, String name);
+}
diff --git a/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/IPreferencesConstants.java b/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/IPreferencesConstants.java
new file mode 100644
index 0000000..be4799d
--- /dev/null
+++ b/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/IPreferencesConstants.java
@@ -0,0 +1,33 @@
+/*******************************************************************************
+ * Copyright (c) 2005 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;
+
+/**
+ * Container for the constants used by this plugin.
+ * @since org.eclipse.equinox.preferences 1.0
+ */
+public interface IPreferencesConstants {
+ /**
+ * Backward compatibilty: name of the original runtime plugin
+ */
+ public static final String RUNTIME_NAME = "org.eclipse.core.runtime"; //$NON-NLS-1$
+
+ /**
+ * Name of this plugin
+ */
+ public static final String PREFERS_NAME = "org.eclipse.equinox.preferences"; //$NON-NLS-1$
+
+ /**
+ * Command line options
+ */
+ public static final String PLUGIN_CUSTOMIZATION = "-plugincustomization"; //$NON-NLS-1$
+
+}
diff --git a/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/ImmutableMap.java b/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/ImmutableMap.java
new file mode 100644
index 0000000..183c695
--- /dev/null
+++ b/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/ImmutableMap.java
@@ -0,0 +1,279 @@
+/*******************************************************************************
+ * Copyright (c) 2005 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 org.eclipse.core.internal.preferences.StringPool;
+
+/**
+ * Hash table of {String --> String}.
+ *
+ * This map handles collisions using linear probing. When elements are
+ * removed, the entire table is rehashed. Thus this map has good space
+ * characteristics, good insertion and iteration performance, but slower
+ * removal performance than a HashMap.
+ * <p>
+ * This map is thread safe because it is immutable. All methods that modify
+ * the map create and return a new map, rather than modifying the receiver.
+ */
+public abstract class ImmutableMap implements Cloneable {
+ static class ArrayMap extends ImmutableMap {
+ private static final float LOAD_FACTOR = 0.45f;
+ /**
+ * number of elements in the table
+ */
+ private int elementSize;
+
+ /**
+ * The table keys
+ */
+ private String[] keyTable;
+
+ private int threshold;
+ private String[] valueTable;
+
+ private ArrayMap() {
+ this(16);
+ }
+
+ ArrayMap(int size) {
+ this.elementSize = 0;
+ //table size must always be a power of two
+ int tableLen = 1;
+ while (tableLen < size)
+ tableLen *= 2;
+ this.keyTable = new String[tableLen];
+ this.valueTable = new String[tableLen];
+ this.threshold = (int) (tableLen * LOAD_FACTOR);
+ }
+
+ public String get(String key) {
+ int lengthMask = keyTable.length - 1;
+ int index = key.hashCode() & lengthMask;
+ String currentKey;
+ while ((currentKey = keyTable[index]) != null) {
+ if (currentKey.equals(key))
+ return valueTable[index];
+ index = (index + 1) & lengthMask;
+ }
+ return null;
+ }
+
+ /**
+ * This method destructively adds the key/value pair to the table.
+ * The caller must ensure the table has an empty slot before calling
+ * this method.
+ * @param key
+ * @param value
+ */
+ protected void internalPut(String key, String value) {
+ int lengthMask = keyTable.length - 1;
+ int index = key.hashCode() & lengthMask;
+ String currentKey;
+ while ((currentKey = keyTable[index]) != null) {
+ if (currentKey.equals(key)) {
+ valueTable[index] = value;
+ return;
+ }
+ index = (index + 1) & lengthMask;
+ }
+ keyTable[index] = key;
+ valueTable[index] = value;
+ ++elementSize;
+ }
+
+ /**
+ * Returns an array of all keys in this map.
+ */
+ public String[] keys() {
+ if (elementSize == 0)
+ return EMPTY_STRING_ARRAY;
+ String[] result = new String[elementSize];
+ int next = 0;
+ for (int i = 0; i < keyTable.length; i++)
+ if (keyTable[i] != null)
+ result[next++] = keyTable[i];
+ return result;
+ }
+
+ public ImmutableMap put(String key, String value) {
+ ArrayMap result;
+ final int oldLen = keyTable.length;
+ if (elementSize + 1 > threshold) {
+ //rehash case
+ String currentKey;
+ result = new ArrayMap(oldLen * 2);
+ for (int i = oldLen; --i >= 0;)
+ if ((currentKey = keyTable[i]) != null)
+ result.internalPut(currentKey, valueTable[i]);
+ } else {
+ result = new ArrayMap(oldLen);
+ System.arraycopy(this.keyTable, 0, result.keyTable, 0, this.keyTable.length);
+ System.arraycopy(this.valueTable, 0, result.valueTable, 0, this.valueTable.length);
+ result.elementSize = this.elementSize;
+ }
+ result.internalPut(key, value);
+ return result;
+ }
+
+ public ImmutableMap removeKey(String key) {
+ final int lengthMask = keyTable.length - 1;
+ int index = key.hashCode() & lengthMask;
+ String currentKey;
+ while ((currentKey = keyTable[index]) != null) {
+ if (currentKey.equals(key)) {
+ if (elementSize <= 1)
+ return EMPTY;
+ //return a new map that includes all keys except the current one
+ ImmutableMap result = createMap((int) (elementSize / LOAD_FACTOR));
+ for (int i = 0; i < index; i++)
+ if ((currentKey = keyTable[i]) != null)
+ result.internalPut(currentKey, valueTable[i]);
+ for (int i = index + 1; i <= lengthMask; i++)
+ if ((currentKey = keyTable[i]) != null)
+ result.internalPut(currentKey, valueTable[i]);
+ return result;
+ }
+ index = (index + 1) & lengthMask;
+ }
+ return this;
+ }
+
+ /* (non-Javadoc
+ * Method declared on IStringPoolParticipant
+ */
+ public void shareStrings(StringPool set) {
+ //copy elements for thread safety
+ String[] array = keyTable;
+ if (array == null)
+ return;
+ for (int i = 0; i < array.length; i++) {
+ String o = array[i];
+ if (o != null)
+ array[i] = set.add(o);
+ }
+ array = valueTable;
+ if (array == null)
+ return;
+ for (int i = 0; i < array.length; i++) {
+ String o = array[i];
+ if (o != null)
+ array[i] = set.add(o);
+ }
+ }
+
+ public int size() {
+ return elementSize;
+ }
+
+ }
+
+ static class EmptyMap extends ImmutableMap {
+ public String get(String value) {
+ return null;
+ }
+
+ public ImmutableMap removeKey(String key) {
+ return this;
+ }
+
+ protected void internalPut(String key, String value) {
+ throw new IllegalStateException();//cannot put elements in the empty map
+ }
+
+ public String[] keys() {
+ return EMPTY_STRING_ARRAY;
+ }
+
+ public ImmutableMap put(String key, String value) {
+ ImmutableMap result = createMap(4);
+ result.internalPut(key, value);
+ return result;
+ }
+
+ public int size() {
+ return 0;
+ }
+ }
+
+ /**
+ * The empty hash map. Since instances are immutable, the empty map
+ * can be a singleton, with accessor methods optimized for the empty map case.
+ */
+ public static final ImmutableMap EMPTY = new EmptyMap();
+
+ protected static final String[] EMPTY_STRING_ARRAY = new String[0];
+
+ /**
+ * Returns the value associated with this key in the map, or
+ * <code>null</code> if the key is not present in the map.
+ * @param key
+ * @return The value associated with this key, or <code>null</code>
+ */
+ public abstract String get(String key);
+
+ protected static ImmutableMap createMap(int i) {
+ if (i <= 0)
+ return EMPTY;
+ return new ArrayMap(i);
+ }
+
+ /**
+ * Destructively adds a key/value pair to this map. The caller must ensure
+ * there is enough room in this map to proceed.
+ *
+ * @param key
+ * @param value
+ */
+ protected abstract void internalPut(String key, String value);
+
+ /**
+ * Returns an array of all keys in this map.
+ */
+ public abstract String[] keys();
+
+ /**
+ * Returns a new map that is equal to this one, except with the given
+ * key/value pair added.
+ *
+ * @param key
+ * @param value
+ * @return The map containing the given key/value pair
+ */
+ public abstract ImmutableMap put(String key, String value);
+
+ /**
+ * Returns a map that is equal to this one, except without the given
+ * key.
+ * @param key
+ * @return A map with the given key removed
+ */
+ public abstract ImmutableMap removeKey(String key);
+
+ /* (non-Javadoc
+ * Method declared on IStringPoolParticipant
+ */
+ public void shareStrings(StringPool set) {
+ }
+
+ /**
+ * Returns the number of keys in this map.
+ * @return the number of keys in this map.
+ */
+ public abstract int size();
+
+ public String toString() {
+ StringBuffer s = new StringBuffer();
+ String[] keys = keys();
+ for (int i = 0, length = keys.length; i < length; i++)
+ s.append(keys[i]).append(" -> ").append(get(keys[i])).append("\n"); //$NON-NLS-2$ //$NON-NLS-1$
+ return s.toString();
+ }
+}
diff --git a/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/InstancePreferences.java b/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/InstancePreferences.java
new file mode 100644
index 0000000..333f83f
--- /dev/null
+++ b/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/InstancePreferences.java
@@ -0,0 +1,211 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2005 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.*;
+import java.util.*;
+import org.eclipse.core.internal.runtime.MetaDataKeeper;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.core.runtime.preferences.IEclipsePreferences;
+import org.eclipse.osgi.service.datalocation.Location;
+
+/**
+ * @since 3.0
+ */
+public class InstancePreferences extends EclipsePreferences {
+
+ // cached values
+ private String qualifier;
+ private int segmentCount;
+ private IEclipsePreferences loadLevel;
+ private IPath location;
+ // cache which nodes have been loaded from disk
+ private static Set loadedNodes = new HashSet();
+ private static boolean initialized = false;
+ private static IPath baseLocation;
+
+ private static IPath getBaseLocation() {
+ // If we are running with -data=@none we won't have an instance location.
+ // By leaving the value of baseLocation as null we still allow the users
+ // to set preferences in this scope but the values will not be persisted
+ // to disk when #flush() is called.
+ if (baseLocation == null) {
+ Location instanceLocation = PreferencesOSGiUtils.getDefault().getInstanceLocation();
+ if (instanceLocation != null && (instanceLocation.isSet() || instanceLocation.allowsDefault()))
+ baseLocation = MetaDataKeeper.getMetaArea().getStateLocation(IPreferencesConstants.RUNTIME_NAME);
+ }
+ return baseLocation;
+ }
+
+ /**
+ * Default constructor. Should only be called by #createExecutableExtension.
+ */
+ public InstancePreferences() {
+ this(null, null);
+ }
+
+ private InstancePreferences(EclipsePreferences parent, String name) {
+ super(parent, name);
+
+ initializeChildren();
+
+ // cache the segment count
+ String path = absolutePath();
+ segmentCount = getSegmentCount(path);
+ if (segmentCount < 2)
+ return;
+
+ // cache the qualifier
+ qualifier = getSegment(path, 1);
+
+ // don't cache the location until later in case instance prefs are
+ // accessed before the instance location is set.
+ }
+
+ protected boolean isAlreadyLoaded(IEclipsePreferences node) {
+ return loadedNodes.contains(node.name());
+ }
+
+ protected void loaded() {
+ loadedNodes.add(name());
+ }
+
+ /**
+ * Load the Eclipse 2.1 preferences for the given bundle. If a file
+ * doesn't exist then assume that conversion has already occurred
+ * and do nothing.
+ */
+ protected void loadLegacy() {
+ IPath path = new Path(absolutePath());
+ if (path.segmentCount() != 2)
+ return;
+ // If we are running with -data=@none we won't have an instance location.
+ if (PreferencesOSGiUtils.getDefault().getInstanceLocation() == null) {
+ if (EclipsePreferences.DEBUG_PREFERENCE_GENERAL)
+ PrefsMessages.message("Cannot load Legacy plug-in preferences since instance location is not set."); //$NON-NLS-1$
+ return;
+ }
+ String bundleName = path.segment(1);
+ // the preferences file is located in the plug-in's state area at a well-known name
+ // don't need to create the directory if there are no preferences to load
+ File prefFile = null;
+ Location instanceLocation = PreferencesOSGiUtils.getDefault().getInstanceLocation();
+ if (instanceLocation != null && instanceLocation.isSet())
+ prefFile = MetaDataKeeper.getMetaArea().getPreferenceLocation(bundleName, false).toFile();
+ if (prefFile == null) {
+ if (EclipsePreferences.DEBUG_PREFERENCE_GENERAL)
+ PrefsMessages.message("Cannot load legacy values because instance location is not set."); //$NON-NLS-1$
+ return;
+ }
+ if (!prefFile.exists()) {
+ // no preference file - that's fine
+ if (EclipsePreferences.DEBUG_PREFERENCE_GENERAL)
+ PrefsMessages.message("Legacy plug-in preference file not found: " + prefFile); //$NON-NLS-1$
+ return;
+ }
+
+ if (EclipsePreferences.DEBUG_PREFERENCE_GENERAL)
+ PrefsMessages.message("Loading legacy preferences from " + prefFile); //$NON-NLS-1$
+
+ // load preferences from file
+ InputStream input = null;
+ Properties values = new Properties();
+ try {
+ input = new BufferedInputStream(new FileInputStream(prefFile));
+ values.load(input);
+ } catch (IOException e) {
+ // problems loading preference store - quietly ignore
+ if (EclipsePreferences.DEBUG_PREFERENCE_GENERAL)
+ PrefsMessages.message("IOException encountered loading legacy preference file " + prefFile); //$NON-NLS-1$
+ return;
+ } finally {
+ if (input != null) {
+ try {
+ input.close();
+ } catch (IOException e) {
+ // ignore problems with close
+ if (EclipsePreferences.DEBUG_PREFERENCE_GENERAL) {
+ PrefsMessages.message("IOException encountered closing legacy preference file " + prefFile); //$NON-NLS-1$
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+
+ // Store values in the preferences object
+ for (Iterator i = values.keySet().iterator(); i.hasNext();) {
+ String key = (String) i.next();
+ String value = values.getProperty(key);
+ // value shouldn't be null but check just in case...
+ if (value != null) {
+ if (EclipsePreferences.DEBUG_PREFERENCE_GENERAL)
+ PrefsMessages.message("Loaded legacy preference: " + key + " -> " + value); //$NON-NLS-1$ //$NON-NLS-2$
+ // call these 2 methods rather than #put() so we don't send out unnecessary notification
+ Object oldValue = internalPut(key, value);
+ if (!value.equals(oldValue))
+ makeDirty();
+ }
+ }
+
+ // Delete the old file so we don't try and load it next time.
+ if (!prefFile.delete())
+ //Only print out message in failure case if we are debugging.
+ if (EclipsePreferences.DEBUG_PREFERENCE_GENERAL)
+ PrefsMessages.message("Unable to delete legacy preferences file: " + prefFile); //$NON-NLS-1$
+ }
+
+ protected IPath getLocation() {
+ if (location == null)
+ location = computeLocation(getBaseLocation(), qualifier);
+ return location;
+ }
+
+ /*
+ * Return the node at which these preferences are loaded/saved.
+ */
+ protected IEclipsePreferences getLoadLevel() {
+ if (loadLevel == null) {
+ if (qualifier == null)
+ return null;
+ // Make it relative to this node rather than navigating to it from the root.
+ // Walk backwards up the tree starting at this node.
+ // This is important to avoid a chicken/egg thing on startup.
+ IEclipsePreferences node = this;
+ for (int i = 2; i < segmentCount; i++)
+ node = (IEclipsePreferences) node.parent();
+ loadLevel = node;
+ }
+ return loadLevel;
+ }
+
+ /*
+ * Initialize the children for the root of this node. Store the names as
+ * keys in the children table so we can lazily load them later.
+ */
+ protected void initializeChildren() {
+ if (initialized || parent == null)
+ return;
+ try {
+ synchronized (this) {
+ String[] names = computeChildren(getBaseLocation());
+ for (int i = 0; i < names.length; i++)
+ addChild(names[i], null);
+ }
+ } finally {
+ initialized = true;
+ }
+ }
+
+ protected EclipsePreferences internalCreate(EclipsePreferences nodeParent, String nodeName, Object context) {
+ return new InstancePreferences(nodeParent, nodeName);
+ }
+}
diff --git a/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/ListenerRegistry.java b/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/ListenerRegistry.java
new file mode 100644
index 0000000..73570e9
--- /dev/null
+++ b/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/ListenerRegistry.java
@@ -0,0 +1,159 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2005 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 org.eclipse.core.runtime.ListenerList;
+
+/**
+ * A class which holds onto a listener list object for a given path.
+ * Typically the path is the absolute path of a preference node.
+ *
+ * @since 3.1
+ */
+public class ListenerRegistry {
+
+ /**
+ * Specialized map-like data structure for storing change listeners.
+ */
+ private static class ListenerMap {
+ private static final int GROW_SIZE = 10;
+ String[] keys;
+ ListenerList[] values;
+
+ /**
+ * Create a map of exactly the specified size.
+ */
+ ListenerMap(int size) {
+ super();
+ this.keys = new String[size];
+ this.values = new ListenerList[size];
+ }
+
+ /**
+ * Return the listener list associated with the given key,
+ * or <code>null</code> if it doesn't exist.
+ */
+ ListenerList get(String key) {
+ if (key == null)
+ throw new NullPointerException();
+ for (int i = 0; i < keys.length; i++)
+ if (key.equals(keys[i]))
+ return values[i];
+ return null;
+ }
+
+ /**
+ * Associate the given listener list with the specified key. Overwrite
+ * an existing association, if applicable.
+ */
+ void put(String key, ListenerList value) {
+ if (key == null)
+ throw new NullPointerException();
+ if (value == null) {
+ remove(key);
+ return;
+ }
+ // replace if exists, keeping track of an empty position
+ int emptyIndex = -1;
+ for (int i = 0; i < keys.length; i++) {
+ String existing = keys[i];
+ if (existing == null) {
+ emptyIndex = i;
+ continue;
+ }
+ if (existing.equals(key)) {
+ values[i] = value;
+ return;
+ }
+ }
+ if (emptyIndex == -1)
+ emptyIndex = grow();
+ keys[emptyIndex] = key;
+ values[emptyIndex] = value;
+ }
+
+ /*
+ * Make the backing arrays larger
+ */
+ private int grow() {
+ int size = keys.length;
+ String[] tempKeys = new String[size + GROW_SIZE];
+ System.arraycopy(keys, 0, tempKeys, 0, size);
+ keys = tempKeys;
+ ListenerList[] tempValues = new ListenerList[size + GROW_SIZE];
+ System.arraycopy(values, 0, tempValues, 0, size);
+ values = tempValues;
+ return size;
+ }
+
+ /**
+ * Remove the association specified by the given key.
+ * Do nothing if none exists.
+ *
+ * Note: Should consider shrinking the array. Hold off for now
+ * as we don't expect #remove to be a common code path.
+ */
+ void remove(String key) {
+ if (key == null)
+ throw new NullPointerException();
+ for (int i = 0; i < keys.length; i++)
+ if (key.equals(keys[i])) {
+ keys[i] = null;
+ values[i] = null;
+ return;
+ }
+ }
+ }
+
+ static final Object[] EMPTY_LIST = new Object[0];
+ ListenerMap registry = new ListenerMap(25);
+
+ /**
+ * Return the listeners for this path or an empty list if none.
+ */
+ public synchronized Object[] getListeners(String path) {
+ ListenerList list = registry.get(path);
+ return list == null ? EMPTY_LIST : list.getListeners();
+ }
+
+ /**
+ * Add the given listener to the listeners registered for this path.
+ * If the listener already exists, then do nothing.
+ */
+ public synchronized void add(String path, Object listener) {
+ ListenerList list = registry.get(path);
+ if (list == null)
+ list = new ListenerList(ListenerList.IDENTITY);
+ list.add(listener);
+ registry.put(path, list);
+ }
+
+ /**
+ * Remove the given listener from this path's collection of
+ * listeners. If it is not associated with this path, then do nothing.
+ */
+ public synchronized void remove(String path, Object listener) {
+ ListenerList list = registry.get(path);
+ if (list == null)
+ return;
+ list.remove(listener);
+ if (list.isEmpty())
+ registry.remove(path);
+ }
+
+ /**
+ * Remove all of the listeners registered under the given path.
+ */
+ public synchronized void clear(String path) {
+ registry.remove(path);
+ }
+
+}
diff --git a/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/LookupOrder.java b/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/LookupOrder.java
new file mode 100644
index 0000000..96284d8
--- /dev/null
+++ b/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/LookupOrder.java
@@ -0,0 +1,33 @@
+/*******************************************************************************
+ * 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;
+
+/**
+ * Object used to store the look-up order for preference
+ * scope searching.
+ *
+ * @since 3.0
+ */
+public class LookupOrder {
+
+ private String[] order;
+
+ LookupOrder(String[] order) {
+ for (int i = 0; i < order.length; i++)
+ if (order[i] == null)
+ throw new IllegalArgumentException();
+ this.order = order;
+ }
+
+ public String[] getOrder() {
+ return order;
+ }
+}
diff --git a/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/PreferenceForwarder.java b/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/PreferenceForwarder.java
new file mode 100644
index 0000000..c0ee012
--- /dev/null
+++ b/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/PreferenceForwarder.java
@@ -0,0 +1,851 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2005 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.*;
+import java.util.Iterator;
+import java.util.Properties;
+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];
+
+ PreferencesService a;
+
+ 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;
+ pluginRoot.addNodeChangeListener(this);
+ }
+
+ /*
+ * @see org.eclipse.core.runtime.preferences.IEclipsePreferences.INodeChangeListener#added(org.eclipse.core.runtime.preferences.IEclipsePreferences.NodeChangeEvent)
+ */
+ public void added(IEclipsePreferences.NodeChangeEvent event) {
+ if (listeners.size() > 0 && pluginID.equals(event.getChild().name()))
+ getPluginPreferences(true).addPreferenceChangeListener(this);
+ }
+
+ /*
+ * @see org.eclipse.core.runtime.preferences.IEclipsePreferences.INodeChangeListener#removed(org.eclipse.core.runtime.preferences.IEclipsePreferences.NodeChangeEvent)
+ */
+ public void removed(IEclipsePreferences.NodeChangeEvent event) {
+ // don't worry about removing the preference change listener since
+ // we won't get any notification from a removed node anyways.
+ }
+
+ /**
+ * Adds a property change listener to this preference object.
+ * Has no affect if the identical listener is already registered.
+ *
+ * @param listener a property change listener
+ */
+ public void addPropertyChangeListener(IPropertyChangeListener listener) {
+ getPluginPreferences(true).addPreferenceChangeListener(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 affect if the listener is not registered.
+ *
+ * @param listener a property change listener
+ */
+ public void removePropertyChangeListener(IPropertyChangeListener listener) {
+ listeners.remove(listener);
+ }
+
+ /**
+ * 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).dirty;
+ }
+
+ /**
+ * 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;
+ }
+}
diff --git a/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/PreferencesOSGiUtils.java b/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/PreferencesOSGiUtils.java
new file mode 100644
index 0000000..9229064
--- /dev/null
+++ b/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/PreferencesOSGiUtils.java
@@ -0,0 +1,196 @@
+/*******************************************************************************
+ * Copyright (c) 2005 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 org.eclipse.equinox.registry.IExtensionRegistry;
+import org.eclipse.osgi.framework.log.FrameworkLog;
+import org.eclipse.osgi.service.datalocation.Location;
+import org.eclipse.osgi.service.debug.DebugOptions;
+import org.osgi.framework.*;
+import org.osgi.service.packageadmin.PackageAdmin;
+import org.osgi.util.tracker.ServiceTracker;
+
+/**
+ * This class contains a set of helper OSGI methods for the Preferences plugin.
+ * The closeServices() method should be called before the plugin is stopped.
+ *
+ * @since org.eclipse.equinox.preferences 1.0
+ */
+public class PreferencesOSGiUtils {
+ private ServiceTracker registryTracker = null;
+ private ServiceTracker logTracker = null;
+ private ServiceTracker initTracker = null;
+ private ServiceTracker debugTracker = null;
+ private ServiceTracker bundleTracker = null;
+ private ServiceTracker configurationLocationTracker = null;
+ private ServiceTracker instanceLocationTracker = null;
+
+ // OSGI system properties. Copied from EclipseStarter
+ public static final String PROP_CONFIG_AREA = "osgi.configuration.area"; //$NON-NLS-1$
+ public static final String PROP_INSTANCE_AREA = "osgi.instance.area"; //$NON-NLS-1$
+
+ private static final PreferencesOSGiUtils singleton = new PreferencesOSGiUtils();
+
+ public static PreferencesOSGiUtils getDefault() {
+ return singleton;
+ }
+
+ /**
+ * Private constructor to block instance creation.
+ */
+ private PreferencesOSGiUtils() {
+ super();
+ initServices();
+ }
+
+ private void initServices() {
+ BundleContext context = Activator.getContext();
+ if (context == null) {
+ PrefsMessages.message("PreferencesOSGiUtils called before plugin started"); //$NON-NLS-1$
+ return;
+ }
+
+ registryTracker = new ServiceTracker(context, IExtensionRegistry.class.getName(), null);
+ registryTracker.open();
+
+ initTracker = new ServiceTracker(context, ILegacyPreferences.class.getName(), null);
+ initTracker.open(true);
+
+ logTracker = new ServiceTracker(context, FrameworkLog.class.getName(), null);
+ logTracker.open();
+
+ debugTracker = new ServiceTracker(context, DebugOptions.class.getName(), null);
+ debugTracker.open();
+
+ bundleTracker = new ServiceTracker(context, PackageAdmin.class.getName(), null);
+ bundleTracker.open();
+
+ // locations
+
+ final String FILTER_PREFIX = "(&(objectClass=org.eclipse.osgi.service.datalocation.Location)(type="; //$NON-NLS-1$
+ Filter filter = null;
+ try {
+ filter = context.createFilter(FILTER_PREFIX + PROP_CONFIG_AREA + "))"); //$NON-NLS-1$
+ } catch (InvalidSyntaxException e) {
+ // ignore this. It should never happen as we have tested the above format.
+ }
+ configurationLocationTracker = new ServiceTracker(context, filter, null);
+ configurationLocationTracker.open();
+
+ try {
+ filter = context.createFilter(FILTER_PREFIX + PROP_INSTANCE_AREA + "))"); //$NON-NLS-1$
+ } catch (InvalidSyntaxException e) {
+ // ignore this. It should never happen as we have tested the above format.
+ }
+ instanceLocationTracker = new ServiceTracker(context, filter, null);
+ instanceLocationTracker.open();
+ }
+
+ void closeServices() {
+ if (registryTracker != null) {
+ registryTracker.close();
+ registryTracker = null;
+ }
+ if (initTracker != null) {
+ initTracker.close();
+ initTracker = null;
+ }
+ if (logTracker != null) {
+ logTracker.close();
+ logTracker = null;
+ }
+ if (debugTracker != null) {
+ debugTracker.close();
+ debugTracker = null;
+ }
+ if (bundleTracker != null) {
+ bundleTracker.close();
+ bundleTracker = null;
+ }
+ if (configurationLocationTracker != null) {
+ configurationLocationTracker.close();
+ configurationLocationTracker = null;
+ }
+ if (instanceLocationTracker != null) {
+ instanceLocationTracker.close();
+ instanceLocationTracker = null;
+ }
+ }
+
+ public IExtensionRegistry getExtensionRegistry() {
+ if (registryTracker != null)
+ return (IExtensionRegistry) registryTracker.getService();
+ PrefsMessages.message("Registry tracker is not set"); //$NON-NLS-1$
+ return null;
+ }
+
+ public ILegacyPreferences getLegacyPreferences() {
+ if (initTracker != null)
+ return (ILegacyPreferences) initTracker.getService();
+ PrefsMessages.message("Legacy preference tracker is not set"); //$NON-NLS-1$
+ return null;
+ }
+
+ public FrameworkLog getFrameworkLog() {
+ if (logTracker != null)
+ return (FrameworkLog) logTracker.getService();
+ PrefsMessages.message("Log tracker is not set"); //$NON-NLS-1$
+ return null;
+ }
+
+ public boolean getBooleanDebugOption(String option, boolean defaultValue) {
+ if (debugTracker == null) {
+ PrefsMessages.message("Debug tracker is not set"); //$NON-NLS-1$
+ return defaultValue;
+ }
+ DebugOptions options = (DebugOptions) debugTracker.getService();
+ if (options != null) {
+ String value = options.getOption(option);
+ if (value != null)
+ return value.equalsIgnoreCase("true"); //$NON-NLS-1$
+ }
+ return defaultValue;
+ }
+
+ public Bundle getBundle(String bundleName) {
+ if (bundleTracker == null) {
+ PrefsMessages.message("Bundle tracker is not set"); //$NON-NLS-1$
+ return null;
+ }
+ PackageAdmin packageAdmin = (PackageAdmin) bundleTracker.getService();
+ if (packageAdmin == null)
+ return null;
+ Bundle[] bundles = packageAdmin.getBundles(bundleName, null);
+ if (bundles == null)
+ return null;
+ //Return the first bundle that is not installed or uninstalled
+ for (int i = 0; i < bundles.length; i++) {
+ if ((bundles[i].getState() & (Bundle.INSTALLED | Bundle.UNINSTALLED)) == 0) {
+ return bundles[i];
+ }
+ }
+ return null;
+ }
+
+ public Location getConfigurationLocation() {
+ if (configurationLocationTracker != null)
+ return (Location) configurationLocationTracker.getService();
+ else
+ return null;
+ }
+
+ public Location getInstanceLocation() {
+ if (instanceLocationTracker != null)
+ return (Location) instanceLocationTracker.getService();
+ else
+ return null;
+ }
+}
diff --git a/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/PreferencesService.java b/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/PreferencesService.java
new file mode 100644
index 0000000..67dbb25
--- /dev/null
+++ b/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/PreferencesService.java
@@ -0,0 +1,1154 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2005 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.*;
+import java.util.*;
+import org.eclipse.core.internal.runtime.RuntimeLog;
+import org.eclipse.core.runtime.*;
+import org.eclipse.core.runtime.preferences.*;
+import org.eclipse.equinox.registry.*;
+import org.eclipse.osgi.util.NLS;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.Constants;
+import org.osgi.service.prefs.BackingStoreException;
+import org.osgi.service.prefs.Preferences;
+
+/**
+ * @since 3.0
+ */
+public class PreferencesService implements IPreferencesService, IRegistryChangeListener {
+
+ /**
+ * The interval between passes over the preference tree to canonicalize
+ * strings.
+ */
+ private static final long STRING_SHARING_INTERVAL = 300000;
+
+ // cheat here and add "project" even though we really shouldn't know about it
+ // because of plug-in dependancies and it being defined in the resources plug-in
+ private static final String[] DEFAULT_DEFAULT_LOOKUP_ORDER = new String[] {"project", //$NON-NLS-1$
+ InstanceScope.SCOPE, //
+ ConfigurationScope.SCOPE, //
+ DefaultScope.SCOPE};
+ private static final char EXPORT_ROOT_PREFIX = '!';
+ private static final char BUNDLE_VERSION_PREFIX = '@';
+ private static final float EXPORT_VERSION = 3;
+ private static final String VERSION_KEY = "file_export_version"; //$NON-NLS-1$
+ private static final String ATTRIBUTE_NAME = "name"; //$NON-NLS-1$
+ private static final String ATTRIBUTE_CLASS = "class"; //$NON-NLS-1$
+ private static final String ELEMENT_SCOPE = "scope"; //$NON-NLS-1$
+ private static final String ELEMENT_MODIFIER = "modifier"; //$NON-NLS-1$
+ private static final String EMPTY_STRING = ""; //$NON-NLS-1$
+
+ private static PreferencesService instance;
+ static final RootPreferences root = new RootPreferences();
+ private static final Map defaultsRegistry = Collections.synchronizedMap(new HashMap());
+ private static final Map scopeRegistry = Collections.synchronizedMap(new HashMap());
+ private ListenerList modifyListeners;
+ /**
+ * The last time analysis was done to remove duplicate strings
+ */
+ private long lastStringSharing = 0;
+
+ /*
+ * Create and return an IStatus object with ERROR severity and the
+ * given message and exception.
+ */
+ private static IStatus createStatusError(String message, Exception e) {
+ return new Status(IStatus.ERROR, PrefsMessages.OWNER_NAME, IStatus.ERROR, message, e);
+ }
+
+ /*
+ * Create and return an IStatus object with WARNING severity and the
+ * given message and exception.
+ */
+ private static IStatus createStatusWarning(String message, Exception e) {
+ return new Status(IStatus.WARNING, PrefsMessages.OWNER_NAME, IStatus.WARNING, message, e);
+ }
+
+ /*
+ * Return the instance.
+ */
+ public static PreferencesService getDefault() {
+ if (instance == null)
+ instance = new PreferencesService();
+ return instance;
+ }
+
+ /**
+ * See who is plugged into the extension point.
+ */
+ private void initializeScopes() {
+ IExtension[] extensions = getPrefExtensions();
+ for (int i = 0; i < extensions.length; i++) {
+ IConfigurationElement[] elements = extensions[i].getConfigurationElements();
+ for (int j = 0; j < elements.length; j++)
+ if (ELEMENT_SCOPE.equalsIgnoreCase(elements[j].getName()))
+ scopeAdded(elements[j]);
+ }
+ PreferencesOSGiUtils.getDefault().getExtensionRegistry().addRegistryChangeListener(this, IPreferencesConstants.RUNTIME_NAME);
+ PreferencesOSGiUtils.getDefault().getExtensionRegistry().addRegistryChangeListener(this, IPreferencesConstants.PREFERS_NAME);
+ }
+
+ private void initializeModifyListeners() {
+ modifyListeners = new ListenerList();
+ IExtension[] extensions = getPrefExtensions();
+ for (int i = 0; i < extensions.length; i++) {
+ IConfigurationElement[] elements = extensions[i].getConfigurationElements();
+ for (int j = 0; j < elements.length; j++)
+ if (ELEMENT_MODIFIER.equalsIgnoreCase(elements[j].getName()))
+ addModifyListener(elements[j]);
+ }
+ PreferencesOSGiUtils.getDefault().getExtensionRegistry().addRegistryChangeListener(this, IPreferencesConstants.RUNTIME_NAME);
+ PreferencesOSGiUtils.getDefault().getExtensionRegistry().addRegistryChangeListener(this, IPreferencesConstants.PREFERS_NAME);
+ }
+
+ static void log(IStatus status) {
+ RuntimeLog.log(status);
+ }
+
+ /*
+ * Abstracted into a separate method to prepare for dynamic awareness.
+ */
+ static void scopeAdded(IConfigurationElement element) {
+ String key = element.getAttribute(ATTRIBUTE_NAME);
+ if (key == null) {
+ String message = NLS.bind(PrefsMessages.preferences_missingScopeAttribute, element.getDeclaringExtension().getUniqueIdentifier());
+ log(createStatusWarning(message, null));
+ return;
+ }
+ scopeRegistry.put(key, element);
+ root.addChild(key, null);
+ }
+
+ /*
+ * Abstracted into a separate method to prepare for dynamic awareness.
+ */
+ private void addModifyListener(IConfigurationElement element) {
+ String key = element.getAttribute(ATTRIBUTE_CLASS);
+ if (key == null) {
+ String message = NLS.bind(PrefsMessages.preferences_missingClassAttribute, element.getDeclaringExtension().getUniqueIdentifier());
+ log(new Status(IStatus.ERROR, PrefsMessages.OWNER_NAME, IStatus.ERROR, message, null));
+ return;
+ }
+ try {
+ Object listener = element.createExecutableExtension(ATTRIBUTE_CLASS);
+ if (!(listener instanceof PreferenceModifyListener)) {
+ log(new Status(IStatus.ERROR, PrefsMessages.OWNER_NAME, IStatus.ERROR, PrefsMessages.preferences_classCastListener, null));
+ return;
+ }
+ modifyListeners.add(listener);
+ } catch (CoreException e) {
+ log(e.getStatus());
+ return;
+ }
+ }
+
+ /*
+ * Abstracted into a separate method to prepare for dynamic awareness.
+ */
+ static void scopeRemoved(String key) {
+ IEclipsePreferences node = (IEclipsePreferences) root.getNode(key, false);
+ if (node != null)
+ root.removeNode(node);
+ else
+ root.removeNode(key);
+ scopeRegistry.remove(key);
+ }
+
+ private PreferencesService() {
+ super();
+ initializeScopes();
+ }
+
+ /*
+ * @see org.eclipse.core.runtime.preferences.IPreferencesService#applyPreferences(org.eclipse.core.runtime.preferences.IExportedPreferences)
+ */
+ public IStatus applyPreferences(IExportedPreferences preferences) throws CoreException {
+ // TODO investigate refactoring to merge with new #apply(IEclipsePreferences, IPreferenceFilter[]) APIs
+ if (preferences == null)
+ throw new IllegalArgumentException();
+
+ if (EclipsePreferences.DEBUG_PREFERENCE_GENERAL)
+ PrefsMessages.message("Applying exported preferences: " + ((ExportedPreferences) preferences).toDeepDebugString()); //$NON-NLS-1$
+
+ final MultiStatus result = new MultiStatus(PrefsMessages.OWNER_NAME, IStatus.OK, PrefsMessages.preferences_applyProblems, null);
+
+ IEclipsePreferences modifiedNode = firePreApplyEvent(preferences);
+
+ // create a visitor to apply the given set of preferences
+ IPreferenceNodeVisitor visitor = new IPreferenceNodeVisitor() {
+ public boolean visit(IEclipsePreferences node) throws BackingStoreException {
+ IEclipsePreferences globalNode;
+ if (node.parent() == null)
+ globalNode = root;
+ else
+ globalNode = (IEclipsePreferences) root.node(node.absolutePath());
+ ExportedPreferences epNode = (ExportedPreferences) node;
+
+ // if this node is an export root then we need to remove
+ // it from the global preferences before continuing.
+ boolean removed = false;
+ if (epNode.isExportRoot()) {
+ if (EclipsePreferences.DEBUG_PREFERENCE_GENERAL)
+ PrefsMessages.message("Found export root: " + epNode.absolutePath()); //$NON-NLS-1$
+ // TODO should only have to do this if any of my children have properties to set
+ globalNode.removeNode();
+ removed = true;
+ }
+
+ // iterate over the preferences in this node and set them
+ // in the global space.
+ String[] keys = epNode.properties.keys();
+ if (keys.length > 0) {
+ // if this node was removed then we need to create a new one
+ if (removed)
+ globalNode = (IEclipsePreferences) root.node(node.absolutePath());
+ for (int i = 0; i < keys.length; i++) {
+ String key = keys[i];
+ // intern strings we import because some people
+ // in their property change listeners use identity
+ // instead of equals. See bug 20193 and 20534.
+ key = key.intern();
+ String value = node.get(key, null);
+ if (value != null) {
+ if (EclipsePreferences.DEBUG_PREFERENCE_SET)
+ PrefsMessages.message("Setting: " + globalNode.absolutePath() + '/' + key + '=' + value); //$NON-NLS-1$
+ globalNode.put(key, value);
+ }
+ }
+ }
+
+ // keep visiting children
+ return true;
+ }
+ };
+
+ try {
+ // start by visiting the root
+ modifiedNode.accept(visitor);
+ } catch (BackingStoreException e) {
+ throw new CoreException(createStatusError(PrefsMessages.preferences_applyProblems, e));
+ }
+
+ // save the prefs
+ try {
+ getRootNode().node(modifiedNode.absolutePath()).flush();
+ } catch (BackingStoreException e) {
+ throw new CoreException(createStatusError(PrefsMessages.preferences_saveProblems, e));
+ }
+
+ if (EclipsePreferences.DEBUG_PREFERENCE_GENERAL)
+ PrefsMessages.message("Current list of all settings: " + ((EclipsePreferences) getRootNode()).toDeepDebugString()); //$NON-NLS-1$
+ //this typically causes a major change to the preference tree, so force string sharing
+ lastStringSharing = 0;
+ shareStrings();
+ return result;
+ }
+
+ /*
+ * Convert the given properties file from legacy format to
+ * one which is Eclipse 3.0 compliant.
+ *
+ * Convert the plug-in version indicator entries to export roots.
+ */
+ private Properties convertFromLegacy(Properties properties) {
+ Properties result = new Properties();
+ String prefix = IPath.SEPARATOR + InstanceScope.SCOPE + IPath.SEPARATOR;
+ for (Iterator i = properties.keySet().iterator(); i.hasNext();) {
+ String key = (String) i.next();
+ String value = properties.getProperty(key);
+ if (value != null) {
+ int index = key.indexOf(IPath.SEPARATOR);
+ if (index == -1) {
+ result.put(BUNDLE_VERSION_PREFIX + key, value);
+ result.put(EXPORT_ROOT_PREFIX + prefix + key, EMPTY_STRING);
+ } else {
+ String path = key.substring(0, index);
+ key = key.substring(index + 1);
+ result.put(EclipsePreferences.encodePath(prefix + path, key), value);
+ }
+ }
+ }
+ return result;
+ }
+
+ /*
+ * Convert the given properties file into a node hierarchy suitable for
+ * importing.
+ */
+ private IExportedPreferences convertFromProperties(Properties properties) {
+ IExportedPreferences result = ExportedPreferences.newRoot();
+ for (Iterator i = properties.keySet().iterator(); i.hasNext();) {
+ String path = (String) i.next();
+ String value = properties.getProperty(path);
+ if (path.charAt(0) == EXPORT_ROOT_PREFIX) {
+ ExportedPreferences current = (ExportedPreferences) result.node(path.substring(1));
+ current.setExportRoot();
+ } else if (path.charAt(0) == BUNDLE_VERSION_PREFIX) {
+ ExportedPreferences current = (ExportedPreferences) result.node(InstanceScope.SCOPE).node(path.substring(1));
+ current.setVersion(value);
+ } else {
+ String[] decoded = EclipsePreferences.decodePath(path);
+ path = decoded[0] == null ? EMPTY_STRING : decoded[0];
+ ExportedPreferences current = (ExportedPreferences) result.node(path);
+ String key = decoded[1];
+ current.put(key, value);
+ }
+ }
+ if (EclipsePreferences.DEBUG_PREFERENCE_GENERAL)
+ PrefsMessages.message("Converted preferences file to IExportedPreferences tree: " + ((ExportedPreferences) result).toDeepDebugString()); //$NON-NLS-1$
+ return result;
+ }
+
+ /*
+ * Return the string which is the scope for the given path.
+ * Return the empty string if it cannot be determined.
+ */
+ String getScope(String path) {
+ if (path == null || path.length() == 0)
+ return EMPTY_STRING;
+ int startIndex = path.indexOf(IPath.SEPARATOR);
+ if (startIndex == -1)
+ return path;
+ if (path.length() == 1)
+ return EMPTY_STRING;
+ int endIndex = path.indexOf(IPath.SEPARATOR, startIndex + 1);
+ if (endIndex == -1)
+ endIndex = path.length();
+ return path.substring(startIndex + 1, endIndex);
+ }
+
+ /*
+ * excludesList is guarenteed not to be null
+ */
+ private Properties convertToProperties(IEclipsePreferences preferences, final String[] excludesList) throws BackingStoreException {
+ final Properties result = new Properties();
+ final int baseLength = preferences.absolutePath().length();
+
+ // create a visitor to do the export
+ IPreferenceNodeVisitor visitor = new IPreferenceNodeVisitor() {
+ public boolean visit(IEclipsePreferences node) throws BackingStoreException {
+ // don't store defaults
+ String absolutePath = node.absolutePath();
+ String scope = getScope(absolutePath);
+ if (DefaultScope.SCOPE.equals(scope))
+ return false;
+ String path = absolutePath.length() <= baseLength ? EMPTY_STRING : EclipsePreferences.makeRelative(absolutePath.substring(baseLength));
+ // check the excludes list to see if this node should be considered
+ for (int i = 0; i < excludesList.length; i++) {
+ String exclusion = EclipsePreferences.makeRelative(excludesList[i]);
+ if (path.startsWith(exclusion))
+ return false;
+ }
+ boolean needToAddVersion = InstanceScope.SCOPE.equals(scope);
+ // check the excludes list for each preference
+ String[] keys = node.keys();
+ for (int i = 0; i < keys.length; i++) {
+ String key = keys[i];
+ boolean ignore = false;
+ for (int j = 0; !ignore && j < excludesList.length; j++)
+ if (EclipsePreferences.encodePath(path, key).startsWith(EclipsePreferences.makeRelative(excludesList[j])))
+ ignore = true;
+ if (!ignore) {
+ String value = node.get(key, null);
+ if (value != null) {
+ if (needToAddVersion) {
+ String bundle = getBundleName(absolutePath);
+ if (bundle != null) {
+ String version = getBundleVersion(bundle);
+ if (version != null)
+ result.put(BUNDLE_VERSION_PREFIX + bundle, version);
+ }
+ needToAddVersion = false;
+ }
+ result.put(EclipsePreferences.encodePath(absolutePath, key), value);
+ }
+ }
+ }
+ return true;
+ }
+ };
+
+ // start by visiting the root that we were passed in
+ preferences.accept(visitor);
+
+ // return the properties object
+ return result;
+ }
+
+ protected IEclipsePreferences createNode(String name) {
+ IScope scope = null;
+ Object value = scopeRegistry.get(name);
+ if (value instanceof IConfigurationElement) {
+ try {
+ scope = (IScope) ((IConfigurationElement) value).createExecutableExtension(ATTRIBUTE_CLASS);
+ scopeRegistry.put(name, scope);
+ } catch (ClassCastException e) {
+ log(createStatusError(PrefsMessages.preferences_classCastScope, e));
+ return new EclipsePreferences(root, name);
+ } catch (CoreException e) {
+ log(e.getStatus());
+ return new EclipsePreferences(root, name);
+ }
+ } else
+ scope = (IScope) value;
+ return scope.create(root, name);
+ }
+
+ /*
+ * @see org.eclipse.core.runtime.preferences.IPreferencesService#exportPreferences(org.eclipse.core.runtime.preferences.IEclipsePreferences, java.io.OutputStream, java.lang.String[])
+ */
+ public IStatus exportPreferences(IEclipsePreferences node, OutputStream output, String[] excludesList) throws CoreException {
+ // TODO investigate refactoring to merge with new #export(IEclipsePreferences, IPreferenceFilter[]) APIs
+ if (node == null || output == null)
+ throw new IllegalArgumentException();
+ Properties properties = null;
+ if (excludesList == null)
+ excludesList = new String[0];
+ try {
+ properties = convertToProperties(node, excludesList);
+ if (properties.isEmpty())
+ return Status.OK_STATUS;
+ properties.put(VERSION_KEY, Float.toString(EXPORT_VERSION));
+ properties.put(EXPORT_ROOT_PREFIX + node.absolutePath(), EMPTY_STRING);
+ } catch (BackingStoreException e) {
+ throw new CoreException(createStatusError(e.getMessage(), e));
+ }
+ try {
+ properties.store(output, null);
+ } catch (IOException e) {
+ throw new CoreException(createStatusError(PrefsMessages.preferences_exportProblems, e));
+ }
+ return Status.OK_STATUS;
+ }
+
+ /*
+ * @see org.eclipse.core.runtime.preferences.IPreferencesService#get(java.lang.String, java.lang.String, org.osgi.service.prefs.Preferences[])
+ */
+ public String get(String key, String defaultValue, Preferences[] nodes) {
+ if (nodes == null)
+ return defaultValue;
+ for (int i = 0; i < nodes.length; i++) {
+ Preferences node = nodes[i];
+ if (node != null) {
+ String result = node.get(key, null);
+ if (result != null)
+ return result;
+ }
+ }
+ return defaultValue;
+ }
+
+ /*
+ * @see org.eclipse.core.runtime.preferences.IPreferencesService#getBoolean(java.lang.String, java.lang.String, boolean, org.eclipse.core.runtime.preferences.IScope[])
+ */
+ public boolean getBoolean(String qualifier, String key, boolean defaultValue, IScopeContext[] scopes) {
+ String result = get(EclipsePreferences.decodePath(key)[1], null, getNodes(qualifier, key, scopes));
+ return result == null ? defaultValue : Boolean.valueOf(result).booleanValue();
+ }
+
+ /*
+ * Return the version for the bundle with the given name. Return null if it
+ * is not known or there is a problem.
+ */
+ String getBundleVersion(String bundleName) {
+ Bundle bundle = PreferencesOSGiUtils.getDefault().getBundle(bundleName);
+ if (bundle != null) {
+ Object version = bundle.getHeaders(EMPTY_STRING).get(Constants.BUNDLE_VERSION);
+ if (version != null && version instanceof String)
+ return (String) version;
+ }
+ return null;
+ }
+
+ /*
+ * Return the name of the bundle from the given path.
+ * It is assumed that that path is:
+ * - absolute
+ * - in the instance scope
+ */
+ String getBundleName(String path) {
+ if (path.length() == 0 || path.charAt(0) != IPath.SEPARATOR)
+ return null;
+ int first = path.indexOf(IPath.SEPARATOR, 1);
+ if (first == -1)
+ return null;
+ int second = path.indexOf(IPath.SEPARATOR, first + 1);
+ return second == -1 ? path.substring(first + 1) : path.substring(first + 1, second);
+ }
+
+ /*
+ * @see org.eclipse.core.runtime.preferences.IPreferencesService#getByteArray(java.lang.String, java.lang.String, byte[], org.eclipse.core.runtime.preferences.IScope[])
+ */
+ public byte[] getByteArray(String qualifier, String key, byte[] defaultValue, IScopeContext[] scopes) {
+ String result = get(EclipsePreferences.decodePath(key)[1], null, getNodes(qualifier, key, scopes));
+ return result == null ? defaultValue : result.getBytes();
+ }
+
+ /*
+ * @see org.eclipse.core.runtime.preferences.IPreferencesService#getDefaultLookupOrder(java.lang.String, java.lang.String)
+ */
+ public String[] getDefaultLookupOrder(String qualifier, String key) {
+ LookupOrder order = (LookupOrder) defaultsRegistry.get(getRegistryKey(qualifier, key));
+ return order == null ? null : order.getOrder();
+ }
+
+ /*
+ * @see org.eclipse.core.runtime.preferences.IPreferencesService#getDouble(java.lang.String, java.lang.String, double, org.eclipse.core.runtime.preferences.IScope[])
+ */
+ public double getDouble(String qualifier, String key, double defaultValue, IScopeContext[] scopes) {
+ String value = get(EclipsePreferences.decodePath(key)[1], null, getNodes(qualifier, key, scopes));
+ if (value == null)
+ return defaultValue;
+ try {
+ return Double.parseDouble(value);
+ } catch (NumberFormatException e) {
+ return defaultValue;
+ }
+ }
+
+ /*
+ * @see org.eclipse.core.runtime.preferences.IPreferencesService#getFloat(java.lang.String, java.lang.String, float, org.eclipse.core.runtime.preferences.IScope[])
+ */
+ public float getFloat(String qualifier, String key, float defaultValue, IScopeContext[] scopes) {
+ String value = get(EclipsePreferences.decodePath(key)[1], null, getNodes(qualifier, key, scopes));
+ if (value == null)
+ return defaultValue;
+ try {
+ return Float.parseFloat(value);
+ } catch (NumberFormatException e) {
+ return defaultValue;
+ }
+ }
+
+ /*
+ * @see org.eclipse.core.runtime.preferences.IPreferencesService#getInt(java.lang.String, java.lang.String, int, org.eclipse.core.runtime.preferences.IScope[])
+ */
+ public int getInt(String qualifier, String key, int defaultValue, IScopeContext[] scopes) {
+ String value = get(EclipsePreferences.decodePath(key)[1], null, getNodes(qualifier, key, scopes));
+ if (value == null)
+ return defaultValue;
+ try {
+ return Integer.parseInt(value);
+ } catch (NumberFormatException e) {
+ return defaultValue;
+ }
+ }
+
+ /*
+ * @see org.eclipse.core.runtime.preferences.IPreferencesService#getLong(java.lang.String, java.lang.String, long, org.eclipse.core.runtime.preferences.IScope[])
+ */
+ public long getLong(String qualifier, String key, long defaultValue, IScopeContext[] scopes) {
+ String value = get(EclipsePreferences.decodePath(key)[1], null, getNodes(qualifier, key, scopes));
+ if (value == null)
+ return defaultValue;
+ try {
+ return Long.parseLong(value);
+ } catch (NumberFormatException e) {
+ return defaultValue;
+ }
+ }
+
+ /*
+ * @see org.eclipse.core.runtime.preferences.IPreferencesService#getLookupOrder(java.lang.String, java.lang.String)
+ */
+ public String[] getLookupOrder(String qualifier, String key) {
+ String[] order = getDefaultLookupOrder(qualifier, key);
+ // if there wasn't an exact match based on both qualifier and simple name
+ // then do a lookup based only on the qualifier
+ if (order == null && key != null)
+ order = getDefaultLookupOrder(qualifier, null);
+ if (order == null)
+ order = DEFAULT_DEFAULT_LOOKUP_ORDER;
+ return order;
+ }
+
+ private Preferences[] getNodes(String qualifier, String key, IScopeContext[] contexts) {
+ String[] order = getLookupOrder(qualifier, key);
+ String childPath = EclipsePreferences.makeRelative(EclipsePreferences.decodePath(key)[0]);
+ ArrayList result = new ArrayList();
+ for (int i = 0; i < order.length; i++) {
+ String scopeString = order[i];
+ boolean found = false;
+ for (int j = 0; contexts != null && j < contexts.length; j++) {
+ IScopeContext context = contexts[j];
+ if (context != null && context.getName().equals(scopeString)) {
+ Preferences node = context.getNode(qualifier);
+ if (node != null) {
+ found = true;
+ if (childPath != null)
+ node = node.node(childPath);
+ result.add(node);
+ }
+ }
+ }
+ if (!found) {
+ Preferences node = getRootNode().node(scopeString).node(qualifier);
+ if (childPath != null)
+ node = node.node(childPath);
+ result.add(node);
+ }
+ found = false;
+ }
+ return (Preferences[]) result.toArray(new Preferences[result.size()]);
+ }
+
+ /*
+ * Convert the given qualifier and key into a key to use in the look-up registry.
+ */
+ private String getRegistryKey(String qualifier, String key) {
+ if (qualifier == null)
+ throw new IllegalArgumentException();
+ if (key == null)
+ return qualifier;
+ return qualifier + '/' + key;
+ }
+
+ /*
+ * @see org.eclipse.core.runtime.preferences.IPreferencesService#getRootNode()
+ */
+
+ public IEclipsePreferences getRootNode() {
+ return root;
+ }
+
+ /*
+ * @see org.eclipse.core.runtime.preferences.IPreferencesService#getString(java.lang.String, java.lang.String, java.lang.String, org.eclipse.core.runtime.preferences.IScope[])
+ */
+ public String getString(String qualifier, String key, String defaultValue, IScopeContext[] scopes) {
+ return get(EclipsePreferences.decodePath(key)[1], defaultValue, getNodes(qualifier, key, scopes));
+ }
+
+ /*
+ * @see org.eclipse.core.runtime.preferences.IPreferencesService#importPreferences(java.io.InputStream)
+ */
+ public IStatus importPreferences(InputStream input) throws CoreException {
+ if (EclipsePreferences.DEBUG_PREFERENCE_GENERAL)
+ PrefsMessages.message("Importing preferences..."); //$NON-NLS-1$
+ return applyPreferences(readPreferences(input));
+ }
+
+ /*
+ * Returns a boolean value indicating whether or not the given Properties
+ * object is the result of a preference export previous to Eclipse 3.0.
+ *
+ * Check the contents of the file. In Eclipse 3.0 we printed out a file
+ * version key.
+ */
+ private boolean isLegacy(Properties properties) {
+ return properties.getProperty(VERSION_KEY) == null;
+ }
+
+ /*
+ * @see org.eclipse.core.runtime.preferences.IPreferencesService#readPreferences(java.io.InputStream)
+ */
+ public IExportedPreferences readPreferences(InputStream input) throws CoreException {
+ if (input == null)
+ throw new IllegalArgumentException();
+
+ if (EclipsePreferences.DEBUG_PREFERENCE_GENERAL)
+ PrefsMessages.message("Reading preferences from stream..."); //$NON-NLS-1$
+
+ // read the file into a properties object
+ Properties properties = new Properties();
+ try {
+ properties.load(input);
+ } catch (IOException e) {
+ throw new CoreException(createStatusError(PrefsMessages.preferences_importProblems, e));
+ } finally {
+ try {
+ input.close();
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+
+ // an empty file is an invalid file format
+ if (properties.isEmpty())
+ throw new CoreException(createStatusError(PrefsMessages.preferences_invalidFileFormat, null));
+
+ // manipulate the file if it from a legacy preference export
+ if (isLegacy(properties)) {
+ if (EclipsePreferences.DEBUG_PREFERENCE_GENERAL)
+ PrefsMessages.message("Read legacy preferences file, converting to 3.0 format..."); //$NON-NLS-1$
+ properties = convertFromLegacy(properties);
+ } else {
+ if (EclipsePreferences.DEBUG_PREFERENCE_GENERAL)
+ PrefsMessages.message("Read preferences file."); //$NON-NLS-1$
+ properties.remove(VERSION_KEY);
+ }
+
+ // convert the Properties object into an object to return
+ return convertFromProperties(properties);
+ }
+
+ public void registryChanged(IRegistryChangeEvent event) {
+ IExtensionDelta[] deltasOld = event.getExtensionDeltas(IPreferencesConstants.RUNTIME_NAME, org.eclipse.core.runtime.Preferences.PT_PREFERENCES);
+ IExtensionDelta[] deltasNew = event.getExtensionDeltas(IPreferencesConstants.PREFERS_NAME, org.eclipse.core.runtime.Preferences.PT_PREFERENCES);
+ IExtensionDelta[] deltas = new IExtensionDelta[deltasOld.length + deltasNew.length];
+ System.arraycopy(deltasOld, 0, deltas, 0, deltasOld.length);
+ System.arraycopy(deltasNew, 0, deltas, deltasOld.length, deltasNew.length);
+
+ if (deltas.length == 0)
+ return;
+ // dynamically adjust the registered scopes
+ for (int i = 0; i < deltas.length; i++) {
+ IConfigurationElement[] elements = deltas[i].getExtension().getConfigurationElements();
+ for (int j = 0; j < elements.length; j++) {
+ switch (deltas[i].getKind()) {
+ case IExtensionDelta.ADDED :
+ if (ELEMENT_SCOPE.equalsIgnoreCase(elements[j].getName()))
+ scopeAdded(elements[j]);
+ break;
+ case IExtensionDelta.REMOVED :
+ String scope = elements[j].getAttribute(ATTRIBUTE_NAME);
+ if (scope != null)
+ scopeRemoved(scope);
+ break;
+ }
+ }
+ }
+ // initialize the preference modify listeners
+ modifyListeners = null;
+ }
+
+ /*
+ * @see org.eclipse.core.runtime.preferences.IPreferencesService#setDefaultLookupOrder(java.lang.String, java.lang.String, java.lang.String[])
+ */
+ public void setDefaultLookupOrder(String qualifier, String key, String[] order) {
+ String registryKey = getRegistryKey(qualifier, key);
+ if (order == null)
+ defaultsRegistry.remove(registryKey);
+ else {
+ LookupOrder obj = new LookupOrder(order);
+ defaultsRegistry.put(registryKey, obj);
+ }
+ }
+
+ /**
+ * Shares all duplicate equal strings referenced by the preference service.
+ */
+ void shareStrings() {
+ long now = System.currentTimeMillis();
+ if (now - lastStringSharing < STRING_SHARING_INTERVAL)
+ return;
+ StringPool pool = new StringPool();
+ root.shareStrings(pool);
+ if (EclipsePreferences.DEBUG_PREFERENCE_GENERAL)
+ System.out.println("Preference string sharing saved: " + pool.getSavedStringCount()); //$NON-NLS-1$
+ lastStringSharing = now;
+ }
+
+ public IStatus validateVersions(IPath path) {
+ final MultiStatus result = new MultiStatus(PrefsMessages.OWNER_NAME, IStatus.INFO, PrefsMessages.preferences_validate, null);
+ IPreferenceNodeVisitor visitor = new IPreferenceNodeVisitor() {
+ public boolean visit(IEclipsePreferences node) {
+ if (!(node instanceof ExportedPreferences))
+ return false;
+
+ // calculate the version in the file
+ ExportedPreferences realNode = (ExportedPreferences) node;
+ String version = realNode.getVersion();
+ if (version == null || !PluginVersionIdentifier.validateVersion(version).isOK())
+ return true;
+ PluginVersionIdentifier versionInFile = new PluginVersionIdentifier(version);
+
+ // calculate the version of the installed bundle
+ String bundleName = getBundleName(node.absolutePath());
+ if (bundleName == null)
+ return true;
+ String stringVersion = getBundleVersion(bundleName);
+ if (stringVersion == null || !PluginVersionIdentifier.validateVersion(stringVersion).isOK())
+ return true;
+ PluginVersionIdentifier versionInMemory = new PluginVersionIdentifier(stringVersion);
+
+ // verify the versions based on the matching rules
+ IStatus verification = validatePluginVersions(bundleName, versionInFile, versionInMemory);
+ if (verification != null)
+ result.add(verification);
+
+ return true;
+ }
+ };
+
+ InputStream input = null;
+ try {
+ input = new BufferedInputStream(new FileInputStream(path.toFile()));
+ IExportedPreferences prefs = readPreferences(input);
+ prefs.accept(visitor);
+ } catch (FileNotFoundException e) {
+ // ignore...if the file does not exist then all is OK
+ } catch (CoreException e) {
+ result.add(createStatusError(PrefsMessages.preferences_validationException, e));
+ } catch (BackingStoreException e) {
+ result.add(createStatusError(PrefsMessages.preferences_validationException, e));
+ }
+ return result;
+ }
+
+ /**
+ * Compares two plugin version identifiers to see if their preferences
+ * are compatible. If they are not compatible, a warning message is
+ * added to the given multistatus, according to the following rules:
+ *
+ * - plugins that differ in service number: no status
+ * - plugins that differ in minor version: WARNING status
+ * - plugins that differ in major version:
+ * - where installed plugin is newer: WARNING status
+ * - where installed plugin is older: ERROR status
+ * @param bundle the name of the bundle
+ * @param pref The version identifer of the preferences to be loaded
+ * @param installed The version identifier of the installed plugin
+ */
+ IStatus validatePluginVersions(String bundle, PluginVersionIdentifier pref, PluginVersionIdentifier installed) {
+ if (installed.getMajorComponent() == pref.getMajorComponent() && installed.getMinorComponent() == pref.getMinorComponent())
+ return null;
+ int severity;
+ if (installed.getMajorComponent() < pref.getMajorComponent())
+ severity = IStatus.ERROR;
+ else
+ severity = IStatus.WARNING;
+ String msg = NLS.bind(PrefsMessages.preferences_incompatible, (new Object[] {pref, bundle, installed}));
+ return new Status(severity, PrefsMessages.OWNER_NAME, 1, msg, null);
+ }
+
+ private IEclipsePreferences mergeTrees(IEclipsePreferences[] trees) throws BackingStoreException {
+ if (trees.length == 1)
+ return trees[0];
+ final IEclipsePreferences result = ExportedPreferences.newRoot();
+ if (trees.length == 0)
+ return result;
+ IPreferenceNodeVisitor visitor = new IPreferenceNodeVisitor() {
+ public boolean visit(IEclipsePreferences node) throws BackingStoreException {
+ Preferences destination = result.node(node.absolutePath());
+ copyFromTo(node, destination, null, 0);
+ return true;
+ }
+ };
+ for (int i = 0; i < trees.length; i++)
+ trees[i].accept(visitor);
+ return result;
+ }
+
+ /*
+ * Return a tree which contains only nodes and keys which are applicable to the given filter.
+ */
+ private IEclipsePreferences trimTree(IEclipsePreferences tree, IPreferenceFilter filter) throws BackingStoreException {
+ IEclipsePreferences result = (IEclipsePreferences) ExportedPreferences.newRoot().node(tree.absolutePath());
+ String[] scopes = filter.getScopes();
+ if (scopes == null)
+ throw new IllegalArgumentException();
+ String treePath = tree.absolutePath();
+ // see if this node is applicable by going over all our scopes
+ for (int i = 0; i < scopes.length; i++) {
+ String scope = scopes[i];
+ Map mapping = filter.getMapping(scope);
+ // if the mapping is null then copy everything if the scope matches
+ if (mapping == null) {
+ // if we are the root node then check our children
+ if (tree.parent() == null && tree.nodeExists(scope))
+ copyFromTo(tree.node(scope), result.node(scope), null, -1);
+ // ensure we are in the correct scope
+ else if (scopeMatches(scope, tree))
+ copyFromTo(tree, result, null, -1);
+ continue;
+ }
+ // iterate over the list of declared nodes
+ for (Iterator iter = mapping.keySet().iterator(); iter.hasNext();) {
+ String nodePath = (String) iter.next();
+ String nodeFullPath = '/' + scope + '/' + nodePath;
+ // if this subtree isn't in a hierarchy we are interested in, then go to the next one
+ if (!nodeFullPath.startsWith(treePath))
+ continue;
+ // get the child node
+ String childPath = nodeFullPath.substring(treePath.length());
+ childPath = EclipsePreferences.makeRelative(childPath);
+ if (tree.nodeExists(childPath)) {
+ PreferenceFilterEntry[] entries;
+ // protect against wrong classes since this is passed in by the user
+ try {
+ entries = (PreferenceFilterEntry[]) mapping.get(nodePath);
+ } catch (ClassCastException e) {
+ log(createStatusError(PrefsMessages.preferences_classCastFilterEntry, e));
+ continue;
+ }
+ String[] keys = null;
+ if (entries != null) {
+ ArrayList list = new ArrayList();
+ for (int j = 0; j < entries.length; j++) {
+ if (entries[j] != null)
+ list.add(entries[j].getKey());
+ }
+ keys = (String[]) list.toArray(new String[list.size()]);
+ }
+ // do infinite depth if there are no keys specified since the parent matched.
+ copyFromTo(tree.node(childPath), result.node(childPath), keys, keys == null ? -1 : 0);
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Return true if the given node is in the specified scope and false othewise.
+ */
+ private boolean scopeMatches(String scope, IEclipsePreferences tree) {
+ // the root isn't in any scope
+ if (tree.parent() == null)
+ return false;
+ // fancy math to get the first segment of the path
+ String path = tree.absolutePath();
+ int index = path.indexOf('/', 1);
+ String sub = path.substring(1, index == -1 ? path.length() : index);
+ return scope.equals(sub);
+ }
+
+ /**
+ * Copy key/value pairs from the source to the destination. If the key list is null
+ * then copy all associations.
+ *
+ * If the depth is 0, then this operation is performed only on the source node. Otherwise
+ * it is performed on the source node's subtree.
+ *
+ * @param depth one of 0 or -1
+ */
+ void copyFromTo(Preferences source, Preferences destination, String[] keys, int depth) throws BackingStoreException {
+ String[] keysToCopy = keys == null ? source.keys() : keys;
+ for (int i = 0; i < keysToCopy.length; i++) {
+ String value = source.get(keysToCopy[i], null);
+ if (value != null)
+ destination.put(keysToCopy[i], value);
+ }
+ if (depth == 0)
+ return;
+ String[] children = source.childrenNames();
+ for (int i = 0; i < children.length; i++)
+ copyFromTo(source.node(children[i]), destination.node(children[i]), keys, depth);
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.core.runtime.preferences.IPreferencesService#exportPreferences(IEclipsePreferences, IPreferenceFilter[], OutputStream)
+ */
+ public void exportPreferences(IEclipsePreferences node, IPreferenceFilter[] filters, OutputStream stream) throws CoreException {
+ if (filters == null || filters.length == 0)
+ return;
+ try {
+ internalExport(node, filters, stream);
+ } catch (BackingStoreException e) {
+ throw new CoreException(createStatusError(PrefsMessages.preferences_exportProblems, e));
+ }
+ }
+
+ /**
+ * Take the preference tree and trim it so it only holds values applying to the given filters.
+ * Then export the resulting tree to the given output stream.
+ */
+ private void internalExport(IEclipsePreferences node, IPreferenceFilter filters[], OutputStream output) throws BackingStoreException, CoreException {
+ ArrayList trees = new ArrayList();
+ for (int i = 0; i < filters.length; i++)
+ trees.add(trimTree(node, filters[i]));
+ IEclipsePreferences toExport = mergeTrees((IEclipsePreferences[]) trees.toArray(new IEclipsePreferences[trees.size()]));
+ exportPreferences(toExport, output, (String[]) null);
+ }
+
+ /* (non-Javadoc)
+ * @see IPreferencesService#matches(IEclipsePreferences, IPreferenceFilter[])
+ */
+ public IPreferenceFilter[] matches(IEclipsePreferences tree, IPreferenceFilter[] filters) throws CoreException {
+ if (filters == null || filters.length == 0)
+ return new IPreferenceFilter[0];
+ try {
+ return internalMatches(tree, filters);
+ } catch (BackingStoreException e) {
+ throw new CoreException(createStatusError(PrefsMessages.preferences_matching, e));
+ }
+ }
+
+ /*
+ * Internal method that collects the matching filters for the given tree and returns them.
+ */
+ private IPreferenceFilter[] internalMatches(IEclipsePreferences tree, IPreferenceFilter[] filters) throws BackingStoreException {
+ ArrayList result = new ArrayList();
+ for (int i = 0; i < filters.length; i++)
+ if (internalMatches(tree, filters[i]))
+ result.add(filters[i]);
+ return (IPreferenceFilter[]) result.toArray(new IPreferenceFilter[result.size()]);
+ }
+
+ private boolean containsKeys(IEclipsePreferences aRoot) throws BackingStoreException {
+ final boolean result[] = new boolean[] {false};
+ IPreferenceNodeVisitor visitor = new IPreferenceNodeVisitor() {
+ public boolean visit(IEclipsePreferences node) throws BackingStoreException {
+ if (node.keys().length != 0)
+ result[0] = true;
+ return !result[0];
+ }
+ };
+ aRoot.accept(visitor);
+ return result[0];
+ }
+
+ /*
+ * Return true if the given tree contains information that the specified filter is interested
+ * in, and false otherwise.
+ */
+ private boolean internalMatches(IEclipsePreferences tree, IPreferenceFilter filter) throws BackingStoreException {
+ String[] scopes = filter.getScopes();
+ if (scopes == null)
+ throw new IllegalArgumentException();
+ String treePath = tree.absolutePath();
+ // see if this node is applicable by going over all our scopes
+ for (int i = 0; i < scopes.length; i++) {
+ String scope = scopes[i];
+ Map mapping = filter.getMapping(scope);
+ // if the mapping is null then we match everything
+ if (mapping == null) {
+ // if we are the root check to see if the scope exists
+ if (tree.parent() == null && tree.nodeExists(scope))
+ return containsKeys((IEclipsePreferences) tree.node(scope));
+ // otherwise check to see if we are in the right scope
+ if (scopeMatches(scope, tree) && containsKeys(tree))
+ return true;
+ continue;
+ }
+ // iterate over the list of declared nodes
+ for (Iterator iter = mapping.keySet().iterator(); iter.hasNext();) {
+ String nodePath = (String) iter.next();
+ String nodeFullPath = '/' + scope + '/' + nodePath;
+ // if this subtree isn't in a hierarchy we are interested in, then go to the next one
+ if (!nodeFullPath.startsWith(treePath))
+ continue;
+ // get the child node
+ String childPath = nodeFullPath.substring(treePath.length());
+ childPath = EclipsePreferences.makeRelative(childPath);
+ if (tree.nodeExists(childPath)) {
+ PreferenceFilterEntry[] entries;
+ // protect against wrong classes since this is user-code
+ try {
+ entries = (PreferenceFilterEntry[]) mapping.get(nodePath);
+ } catch (ClassCastException e) {
+ log(createStatusError(PrefsMessages.preferences_classCastFilterEntry, e));
+ continue;
+ }
+ // if there are no entries defined then we return false even if we
+ // are supposed to match on the existance of the node as a whole (bug 88820)
+ Preferences child = tree.node(childPath);
+ if (entries == null)
+ return child.keys().length != 0 || child.childrenNames().length != 0;
+ // otherwise check to see if we have any applicable keys
+ for (int j = 0; j < entries.length; j++) {
+ if (entries[j] != null && child.get(entries[j].getKey(), null) != null)
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.core.runtime.preferences.IPreferencesService#applyPreferences(org.eclipse.core.runtime.preferences.IEclipsePreferences, org.eclipse.core.runtime.preferences.IPreferenceFilter[])
+ */
+ public void applyPreferences(IEclipsePreferences tree, IPreferenceFilter[] filters) throws CoreException {
+ if (filters == null || filters.length == 0)
+ return;
+ try {
+ internalApply(tree, filters);
+ //this typically causes a major change to the preference tree, so force string sharing
+ lastStringSharing = 0;
+ shareStrings();
+ } catch (BackingStoreException e) {
+ throw new CoreException(createStatusError(PrefsMessages.preferences_applyProblems, e));
+ }
+ }
+
+ /**
+ * Filter the given tree so it only contains values which apply to the specified filters
+ * then apply the resulting tree to the main preference tree.
+ */
+ private void internalApply(IEclipsePreferences tree, IPreferenceFilter[] filters) throws BackingStoreException {
+ ArrayList trees = new ArrayList();
+ for (int i = 0; i < filters.length; i++)
+ trees.add(trimTree(tree, filters[i]));
+ // merge the union of the matching filters
+ IEclipsePreferences toApply = mergeTrees((IEclipsePreferences[]) trees.toArray(new IEclipsePreferences[trees.size()]));
+
+ // fire an event to give people a chance to modify the tree
+ toApply = firePreApplyEvent(toApply);
+
+ // actually apply the settings
+ IPreferenceNodeVisitor visitor = new IPreferenceNodeVisitor() {
+ public boolean visit(IEclipsePreferences node) throws BackingStoreException {
+ String[] keys = node.keys();
+ if (keys.length == 0)
+ return true;
+ copyFromTo(node, getRootNode().node(node.absolutePath()), keys, 0);
+ return true;
+ }
+ };
+ toApply.accept(visitor);
+ }
+
+ /*
+ * Give clients a chance to modify the tree before it is applied globally
+ */
+ private IEclipsePreferences firePreApplyEvent(IEclipsePreferences tree) {
+ final IEclipsePreferences[] result = new IEclipsePreferences[] {tree};
+ if (modifyListeners == null)
+ initializeModifyListeners();
+ Object[] listeners = modifyListeners.getListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ final PreferenceModifyListener listener = (PreferenceModifyListener) listeners[i];
+ ISafeRunnable job = new ISafeRunnable() {
+ public void handleException(Throwable exception) {
+ // already logged in Platform#run()
+ }
+
+ public void run() throws Exception {
+ result[0] = listener.preApply(result[0]);
+ }
+ };
+ SafeRunner.run(job);
+ }
+ return result[0];
+ }
+
+ // Store this around for performance
+ private final static IExtension[] emptyExtensionArray = new IExtension[0];
+
+ public static IExtension[] getPrefExtensions() {
+ IExtensionRegistry registry = PreferencesOSGiUtils.getDefault().getExtensionRegistry();
+ IExtension[] extensionsOld = emptyExtensionArray;
+ IExtension[] extensionsNew = emptyExtensionArray;
+ // "old"
+ IExtensionPoint pointOld = registry.getExtensionPoint(IPreferencesConstants.RUNTIME_NAME, org.eclipse.core.runtime.Preferences.PT_PREFERENCES);
+ if (pointOld != null)
+ extensionsOld = pointOld.getExtensions();
+ // "new"
+ IExtensionPoint pointNew = registry.getExtensionPoint(IPreferencesConstants.PREFERS_NAME, org.eclipse.core.runtime.Preferences.PT_PREFERENCES);
+ if (pointNew != null)
+ extensionsNew = pointNew.getExtensions();
+ // combine
+ IExtension[] extensions = new IExtension[extensionsOld.length + extensionsNew.length];
+ System.arraycopy(extensionsOld, 0, extensions, 0, extensionsOld.length);
+ System.arraycopy(extensionsNew, 0, extensions, extensionsOld.length, extensionsNew.length);
+
+ if (extensions.length == 0) {
+ if (EclipsePreferences.DEBUG_PREFERENCE_GENERAL)
+ PrefsMessages.message("No extensions for org.eclipse.core.contenttype."); //$NON-NLS-1$
+ }
+
+ return extensions;
+ }
+
+}
diff --git a/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/PrefsMessages.java b/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/PrefsMessages.java
new file mode 100644
index 0000000..87f34dd
--- /dev/null
+++ b/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/PrefsMessages.java
@@ -0,0 +1,69 @@
+/**********************************************************************
+ * Copyright (c) 2005 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 - Initial API and implementation
+ **********************************************************************/
+package org.eclipse.core.internal.preferences;
+
+import java.util.Date;
+import org.eclipse.osgi.util.NLS;
+
+// Runtime plugin message catalog
+public class PrefsMessages extends NLS {
+ /**
+ * The unique identifier constant of this plug-in.
+ */
+ public static final String OWNER_NAME = "org.eclipse.equinox.preferences"; //$NON-NLS-1$
+
+ private static final String BUNDLE_NAME = "org.eclipse.core.internal.preferences.messages"; //$NON-NLS-1$
+
+ // Preferences
+ public static String preferences_applyProblems;
+ public static String preferences_classCastScope;
+ public static String preferences_classCastListener;
+ public static String preferences_classCastFilterEntry;
+ public static String preferences_errorWriting;
+ public static String preferences_exportProblems;
+ public static String preferences_failedDelete;
+ public static String preferences_fileNotFound;
+ public static String preferences_importProblems;
+ public static String preferences_incompatible;
+ public static String preferences_invalidExtensionSuperclass;
+ public static String preferences_invalidFileFormat;
+ public static String preferences_loadException;
+ public static String preferences_matching;
+ public static String preferences_missingClassAttribute;
+ public static String preferences_missingScopeAttribute;
+ public static String preferences_removedNode;
+ public static String preferences_saveException;
+ public static String preferences_saveProblems;
+ public static String preferences_validate;
+ public static String preferences_validationException;
+
+ static {
+ // load message values from bundle file
+ reloadMessages();
+ }
+
+ public static void reloadMessages() {
+ NLS.initializeMessages(BUNDLE_NAME, PrefsMessages.class);
+ }
+
+ /**
+ * Print a debug message to the console.
+ * Pre-pend the message with the current date and the name of the current thread.
+ */
+ public static void message(String message) {
+ StringBuffer buffer = new StringBuffer();
+ buffer.append(new Date(System.currentTimeMillis()));
+ buffer.append(" - ["); //$NON-NLS-1$
+ buffer.append(Thread.currentThread().getName());
+ buffer.append("] "); //$NON-NLS-1$
+ buffer.append(message);
+ System.out.println(buffer.toString());
+ }
+}
\ No newline at end of file
diff --git a/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/RootPreferences.java b/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/RootPreferences.java
new file mode 100644
index 0000000..d660842
--- /dev/null
+++ b/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/RootPreferences.java
@@ -0,0 +1,129 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2005 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 org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.preferences.IEclipsePreferences;
+import org.osgi.service.prefs.BackingStoreException;
+import org.osgi.service.prefs.Preferences;
+
+/**
+ * @since 3.0
+ */
+public class RootPreferences extends EclipsePreferences {
+
+ /**
+ * Default constructor.
+ */
+ public RootPreferences() {
+ super(null, ""); //$NON-NLS-1$
+ }
+
+ /*
+ * @see org.osgi.service.prefs.Preferences#flush()
+ */
+ public void flush() throws BackingStoreException {
+ // flush all children
+ BackingStoreException exception = null;
+ String[] names = childrenNames();
+ for (int i = 0; i < names.length; i++) {
+ try {
+ node(names[i]).flush();
+ } catch (BackingStoreException e) {
+ // store the first exception we get and still try and flush
+ // the rest of the children.
+ if (exception != null)
+ exception = e;
+ }
+ }
+ if (exception != null)
+ throw exception;
+ }
+
+ /*
+ * @see EclipsePreferences#getChild(String, Plugin)
+ */
+ protected synchronized IEclipsePreferences getChild(String key, Object context) {
+ Object value = null;
+ IEclipsePreferences child = null;
+ if (children != null)
+ value = children.get(key);
+ if (value != null) {
+ if (value instanceof IEclipsePreferences)
+ return (IEclipsePreferences) value;
+ //lazy initialization
+ child = PreferencesService.getDefault().createNode(key);
+ addChild(key, child);
+ }
+ return child;
+ }
+
+ /*
+ * @see EclipsePreferences#getChildren()
+ */
+ protected synchronized IEclipsePreferences[] getChildren() {
+ //must perform lazy initialization of child nodes
+ String[] childNames = childrenNames();
+ IEclipsePreferences[] childNodes = new IEclipsePreferences[childNames.length];
+ for (int i = 0; i < childNames.length; i++)
+ childNodes[i] = getChild(childNames[i], null);
+ return childNodes;
+ }
+
+ /*
+ * @see Preferences#node(String)
+ */
+ public Preferences node(String path) {
+ return getNode(path, true); // create if not found
+ }
+
+ public Preferences getNode(String path, boolean create) {
+ if (path.length() == 0 || (path.length() == 1 && path.charAt(0) == IPath.SEPARATOR))
+ return this;
+ int startIndex = path.charAt(0) == IPath.SEPARATOR ? 1 : 0;
+ int endIndex = path.indexOf(IPath.SEPARATOR, startIndex + 1);
+ String scope = path.substring(startIndex, endIndex == -1 ? path.length() : endIndex);
+ IEclipsePreferences child;
+ if (create) {
+ child = getChild(scope, null);
+ if (child == null) {
+ child = new EclipsePreferences(this, scope);
+ addChild(scope, child);
+ }
+ } else {
+ child = getChild(scope, null, false);
+ if (child == null)
+ return null;
+ }
+ return child.node(endIndex == -1 ? "" : path.substring(endIndex + 1)); //$NON-NLS-1$
+ }
+
+ /*
+ * @see org.osgi.service.prefs.Preferences#sync()
+ */
+ public void sync() throws BackingStoreException {
+ // sync all children
+ BackingStoreException exception = null;
+ String[] names = childrenNames();
+ for (int i = 0; i < names.length; i++) {
+ try {
+ node(names[i]).sync();
+ } catch (BackingStoreException e) {
+ // store the first exception we get and still try and sync
+ // the rest of the children.
+ if (exception != null)
+ exception = e;
+ }
+ }
+ if (exception != null)
+ throw exception;
+ }
+}
diff --git a/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/StringPool.java b/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/StringPool.java
new file mode 100644
index 0000000..5d361aa
--- /dev/null
+++ b/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/StringPool.java
@@ -0,0 +1,68 @@
+/*******************************************************************************
+ * Copyright (c) 2005 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 - Initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.internal.preferences;
+
+import java.util.HashMap;
+
+/**
+ * A string pool is used for sharing strings in a way that eliminates duplicate
+ * equal strings. A string pool instance can be maintained over a long period
+ * of time, or used as a temporary structure during a string sharing pass over
+ * a data structure.
+ * <p>
+ * This class is not intended to be subclassed by clients.
+ * </p>
+ *
+ * Note: This class is copied from org.eclipse.core.resources
+ *
+ * @since 3.1
+ */
+public final class StringPool {
+ private int savings;
+ private final HashMap map = new HashMap();
+
+ /**
+ * Adds a <code>String</code> to the pool. Returns a <code>String</code>
+ * that is equal to the argument but that is unique within this pool.
+ * @param string The string to add to the pool
+ * @return A string that is equal to the argument.
+ */
+ public String add(String string) {
+ if (string == null)
+ return string;
+ Object result = map.get(string);
+ if (result != null) {
+ if (result != string)
+ savings += 44 + 2 * string.length();
+ return (String) result;
+ }
+ map.put(string, string);
+ return string;
+ }
+
+ /**
+ * Returns an estimate of the size in bytes that was saved by sharing strings in
+ * the pool. In particular, this returns the size of all strings that were added to the
+ * pool after an equal string had already been added. This value can be used
+ * to estimate the effectiveness of a string sharing operation, in order to
+ * determine if or when it should be performed again.
+ *
+ * In some cases this does not precisely represent the number of bytes that
+ * were saved. For example, say the pool already contains string S1. Now
+ * string S2, which is equal to S1 but not identical, is added to the pool five
+ * times. This method will return the size of string S2 multiplied by the
+ * number of times it was added, even though the actual savings in this case
+ * is only the size of a single copy of S2.
+ */
+ public int getSavedStringCount() {
+ return savings;
+ }
+}
\ No newline at end of file
diff --git a/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/messages.properties b/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/messages.properties
new file mode 100644
index 0000000..abfb646
--- /dev/null
+++ b/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/messages.properties
@@ -0,0 +1,34 @@
+###############################################################################
+# Copyright (c) 2000, 2005 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
+###############################################################################
+### Runtime preferences plugin messages
+
+### Preferences
+preferences_applyProblems=Problems applying preference changes.
+preferences_classCastScope=Extensions to org.eclipse.core.runtime.preferences extension point must implement the IScope interface.
+preferences_classCastListener=Preference modify listeners must subclass the PreferenceModifyListener class.
+preferences_classCastFilterEntry=Preference filter mappings must be instances of the class PreferenceFilterEntry.
+preferences_errorWriting=Error writing preference file {0}. {1}
+preferences_exportProblems=Problems exporting preferences.
+preferences_failedDelete=Failed to delete preferences file: {0}.
+preferences_fileNotFound=Preference export file not found: {0}.
+preferences_importProblems=Problems importing preferences.
+preferences_incompatible=The preference file contains preferences for version \"{0}\" of plug-in \"{1}\", but version \"{2}\" is currently installed.
+preferences_invalidExtensionSuperclass=Extension does not extend class AbstractPreferenceInitializer.
+preferences_invalidFileFormat=Invalid preference file format.
+preferences_loadException=Exception loading preferences from: {0}.
+preferences_matching=Exception while matching preference filters.
+preferences_missingClassAttribute= Missing \"class\" attribute in \"preference\" element in extension declaration for: {0}.
+preferences_missingScopeAttribute= Missing \"scope\" attribute in \"preference\" element in extension declaration for: {0}.
+preferences_removedNode=Preference node \"{0}\" has been removed.
+preferences_saveException=Exception saving preferences to: {0}.
+preferences_saveProblems=Problems saving preferences.
+preferences_validate=Some preferences may not be compatible with the currently installed plug-ins.
+preferences_validationException=Exception while validating bundle versions.
diff --git a/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/runtime/Preferences.java b/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/runtime/Preferences.java
new file mode 100644
index 0000000..8d882ce
--- /dev/null
+++ b/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/runtime/Preferences.java
@@ -0,0 +1,1284 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2005 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.runtime;
+
+import java.io.*;
+import java.util.*;
+import org.eclipse.core.internal.preferences.PreferencesService;
+import org.eclipse.core.internal.preferences.PrefsMessages;
+import org.eclipse.core.runtime.preferences.*;
+import org.eclipse.osgi.util.NLS;
+
+/**
+ * A table of preference settings, mapping named properties to values. Property
+ * names are non-empty strings; property values can be either booleans,
+ * non-null strings, or values of one of the primitive number types.
+ * The table consists of two, sparse, layers: the lower layer holds default values
+ * for properties, and the upper layer holds explicitly set values for properties.
+ * Normal retrieval looks for an explicitly set value for the given property in
+ * the upper layer; if there is nothing for that property in the upper layer, it
+ * next looks for a default value for the given property in the lower layer; if
+ * there is nothing for that property in the lower layer, it returns a standard
+ * default-default value. The default-default values for the primitive types are
+ * as follows:
+ * <ul>
+ * <li><code>boolean</code> = <code>false</code></li>
+ * <li><code>double</code> = <code>0.0</code></li>
+ * <li><code>float</code> = <code>0.0f</code></li>
+ * <li><code>int</code> = <code>0</code></li>
+ * <li><code>long</code> = <code>0L</code></li>
+ * <li><code>String</code> = <code>""</code> (the empty string)</li>
+ * </ul>
+ * <p>
+ * Internally, all properties values (in both layers) are stored as strings.
+ * Standard conversions to and from numeric and boolean types are performed on
+ * demand.
+ * </p>
+ * <p>
+ * The typical usage is to establish the defaults for all known properties
+ * and then restore previously stored values for properties whose values
+ * were explicitly set. The existing settings can be changed and new properties
+ * can be set (<code>setValue</code>). If the values specified is the same as
+ * the default value, the explicit setting is deleted from the top layer.
+ * It is also possible to reset a property value back to the default value
+ * using <code>setToDefault</code>. After the properties have been modified,
+ * the properties with explicit settings are written to disk. The default values
+ * are never saved. This two-tiered approach
+ * to saving and restoring property setting minimizes the number of properties
+ * that need to be persisted; indeed, the normal starting state does not require
+ * storing any properties at all. It also makes it easy to use different
+ * default settings in different environments while maintaining just those
+ * property settings the user has adjusted.
+ * </p>
+ * <p>
+ * A property change event is reported whenever a property's value actually
+ * changes (either through <code>setValue</code>, <code>setToDefault</code>).
+ * Note, however, that manipulating default values (with <code>setDefault</code>)
+ * does not cause any events to be reported.
+ * </p>
+ * <p>
+ * Clients may instantiate this class. This class was not designed to be
+ * subclassed.
+ * </p>
+ * <p>
+ * The implementation is based on a pair of internal
+ * <code>java.util.Properties</code> objects, one holding explicitly set values
+ * (set using <code>setValue</code>), the other holding the default values
+ * (set using <code>setDefaultValue</code>). The <code>load</code> and
+ * <code>store</code> methods persist the non-default property values to
+ * streams (the default values are not saved).
+ * </p>
+ * <p>
+ * If a client sets a default value to be equivalent to the default-default for that
+ * type, the value is still known to the preference store as having a default value.
+ * That is, the name will still be returned in the result of the <code>defaultPropertyNames</code>
+ * and <code>contains</code> methods.
+ * </p>
+ *
+ * @since 2.0
+ */
+public class Preferences {
+
+ /**
+ * The default-default value for boolean properties (<code>false</code>).
+ */
+ public static final boolean BOOLEAN_DEFAULT_DEFAULT = false;
+
+ /**
+ * The default-default value for double properties (<code>0.0</code>).
+ */
+ public static final double DOUBLE_DEFAULT_DEFAULT = 0.0;
+
+ /**
+ * The default-default value for float properties (<code>0.0f</code>).
+ */
+ public static final float FLOAT_DEFAULT_DEFAULT = 0.0f;
+
+ /**
+ * The default-default value for int properties (<code>0</code>).
+ */
+ public static final int INT_DEFAULT_DEFAULT = 0;
+
+ /**
+ * The default-default value for long properties (<code>0L</code>).
+ */
+ public static final long LONG_DEFAULT_DEFAULT = 0L;
+
+ /**
+ * The default-default value for String properties (<code>""</code>).
+ */
+ public static final String STRING_DEFAULT_DEFAULT = ""; //$NON-NLS-1$
+
+ /**
+ * The string representation used for <code>true</code>
+ * (<code>"true"</code>).
+ */
+ protected static final String TRUE = "true"; //$NON-NLS-1$
+
+ /**
+ * The string representation used for <code>false</code>
+ * (<code>"false"</code>).
+ */
+ protected static final String FALSE = "false"; //$NON-NLS-1$
+
+ /**
+ * Singleton empty string array (optimization)
+ */
+ private static final String[] EMPTY_STRING_ARRAY = new String[0];
+
+ /**
+ * The simple identifier constant (value "<code>preferences</code>") of
+ * the extension point of the Core Runtime plug-in where plug-ins declare
+ * extensions to the preference facility. A plug-in may define any number
+ * of preference extensions.
+ *
+ * @since org.eclipse.equinox.preferences 1.0
+ */
+ public static final String PT_PREFERENCES = "preferences"; //$NON-NLS-1$
+
+ /**
+ * The name of the file (value <code>"preferences.ini"</code>) in a
+ * plug-in's (read-only) directory that, when present, contains values that
+ * override the normal default values for this plug-in's preferences.
+ * <p>
+ * The format of the file is as per <code>java.io.Properties</code> where
+ * the keys are property names and values are strings.
+ * </p>
+ *
+ * @since 3.3
+ */
+ public static final String PREFERENCES_DEFAULT_OVERRIDE_BASE_NAME = "preferences"; //$NON-NLS-1$
+ public static final String PREFERENCES_DEFAULT_OVERRIDE_FILE_NAME = PREFERENCES_DEFAULT_OVERRIDE_BASE_NAME + ".ini"; //$NON-NLS-1$
+
+ /**
+ * An event object describing a change to a named property.
+ * <p>
+ * The preferences object reports property change events for internal state
+ * changes that may be of interest to external parties. A special listener
+ * interface (<code>Preferences.IPropertyChangeListener</code>) is
+ * defined for this purpose. Listeners are registered via the
+ * <code>Preferences.addPropertyChangeListener</code> method.
+ * </p>
+ * <p>
+ * Clients cannot instantiate or subclass this class.
+ * </p>
+ *
+ * @see Preferences#addPropertyChangeListener(Preferences.IPropertyChangeListener)
+ * @see Preferences.IPropertyChangeListener
+ */
+ public static class PropertyChangeEvent extends EventObject {
+ /**
+ * All serializable objects should have a stable serialVersionUID
+ */
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * The name of the changed property.
+ */
+ private String propertyName;
+
+ /**
+ * The old value of the changed property, or <code>null</code> if
+ * not known or not relevant.
+ */
+ private Object oldValue;
+
+ /**
+ * The new value of the changed property, or <code>null</code> if
+ * not known or not relevant.
+ */
+ private Object newValue;
+
+ /**
+ * Creates a new property change event.
+ *
+ * @param source the object whose property has changed
+ * @param property the property that has changed (must not be
+ * <code>null</code>)
+ * @param oldValue the old value of the property, or
+ * <code>null</code> if none
+ * @param newValue the new value of the property, or
+ * <code>null</code> if none
+ */
+ protected PropertyChangeEvent(Object source, String property, Object oldValue, Object newValue) {
+
+ super(source);
+ if (property == null) {
+ throw new IllegalArgumentException();
+ }
+ this.propertyName = property;
+ this.oldValue = oldValue;
+ this.newValue = newValue;
+ }
+
+ /**
+ * Returns the name of the property that changed.
+ * <p>
+ * Warning: there is no guarantee that the property name returned
+ * is a constant string. Callers must compare property names using
+ * <code>equals</code>, not ==.
+ *</p>
+ *
+ * @return the name of the property that changed
+ */
+ public String getProperty() {
+ return propertyName;
+ }
+
+ /**
+ * Returns the new value of the property.
+ *
+ * @return the new value, or <code>null</code> if not known
+ * or not relevant
+ */
+ public Object getNewValue() {
+ return newValue;
+ }
+
+ /**
+ * Returns the old value of the property.
+ *
+ * @return the old value, or <code>null</code> if not known
+ * or not relevant
+ */
+ public Object getOldValue() {
+ return oldValue;
+ }
+ }
+
+ /**
+ * Listener for property changes.
+ * <p>
+ * Usage:
+ * <pre>
+ * Preferences.IPropertyChangeListener listener =
+ * new Preferences.IPropertyChangeListener() {
+ * public void propertyChange(Preferences.PropertyChangeEvent event) {
+ * ... // code to deal with occurrence of property change
+ * }
+ * };
+ * emitter.addPropertyChangeListener(listener);
+ * ...
+ * emitter.removePropertyChangeListener(listener);
+ * </pre>
+ * </p>
+ * <p>
+ * <em>Note:</em> Depending on the means in which the property
+ * values changed, the old and new values for the property can
+ * be either typed, a string representation of the value, or <code>null</code>.
+ * Clients who wish to behave properly in all cases should all
+ * three cases in their implementation of the property change listener.
+ * </p>
+ */
+ public interface IPropertyChangeListener extends EventListener {
+
+ /**
+ * Notification that a property has changed.
+ * <p>
+ * This method gets called when the observed object fires a property
+ * change event.
+ * </p>
+ *
+ * @param event the property change event object describing which
+ * property changed and how
+ */
+ public void propertyChange(Preferences.PropertyChangeEvent event);
+ }
+
+ /**
+ * List of registered listeners (element type:
+ * <code>IPropertyChangeListener</code>).
+ * These listeners are to be informed when the current value of a property
+ * changes.
+ */
+ protected ListenerList listeners = new ListenerList();
+
+ /**
+ * The mapping from property name to
+ * property value (represented as strings).
+ */
+ private Properties properties;
+
+ /**
+ * The mapping from property name to
+ * default property value (represented as strings);
+ * <code>null</code> if none.
+ */
+ private Properties defaultProperties;
+
+ /**
+ * Indicates whether a value has been changed by <code>setToDefault</code>
+ * or <code>setValue</code>; initially <code>false</code>.
+ */
+ protected boolean dirty = false;
+
+ /**
+ * Exports all non-default-valued preferences for all installed plugins to the
+ * provided file. If a file already exists at the given location, it will be deleted.
+ * If there are no preferences to export, no file will be written.
+ * <p>
+ * The file that is written can be read later using the importPreferences method.
+ * </p>
+ * @param path The absolute file system path of the file to export preferences to.
+ * @exception CoreException if this method fails. Reasons include:
+ * <ul>
+ * <li> The file could not be written.</li>
+ * </ul>
+ * @see #importPreferences(IPath)
+ * @see #validatePreferenceVersions(IPath)
+ */
+ public static void exportPreferences(IPath path) throws CoreException {
+ File file = path.toFile();
+ if (file.exists())
+ file.delete();
+ file.getParentFile().mkdirs();
+ IPreferencesService service = PreferencesService.getDefault();
+ OutputStream output = null;
+ FileOutputStream fos = null;
+ try {
+ fos = new FileOutputStream(file);
+ output = new BufferedOutputStream(fos);
+ IEclipsePreferences node = (IEclipsePreferences) service.getRootNode().node(InstanceScope.SCOPE);
+ service.exportPreferences(node, output, (String[]) null);
+ output.flush();
+ fos.getFD().sync();
+ } catch (IOException e) {
+ String message = NLS.bind(PrefsMessages.preferences_errorWriting, file, e.getMessage());
+ IStatus status = new Status(IStatus.ERROR, PrefsMessages.OWNER_NAME, IStatus.ERROR, message, e);
+ throw new CoreException(status);
+ } finally {
+ if (output != null)
+ try {
+ output.close();
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+ }
+
+ /**
+ * Loads the plugin preferences from the given file, and replaces all
+ * non-default-valued preferences for all plugins with the values from this file.
+ * <p>
+ * If the file contains preferences for plug-ins that don't exist in the current
+ * install, they are ignored. This method does not validate if the plug-in
+ * versions in the preference file match the currently installed plug-ins.
+ * Clients should first call validatePreferenceVersions on the file to ensure
+ * that the versions are compatible.
+ * </p>
+ * <p>
+ * The file must have been written by the exportPreferences method.
+ * </p>
+ * @param path The absolute file system path of the file to import preferences from.
+ * @exception CoreException if this method fails. Reasons include:
+ * <ul>
+ * <li> The file does not exist.</li>
+ * <li> The file could not be read.</li>
+ * </ul>
+ * @see #exportPreferences(IPath)
+ * @see #validatePreferenceVersions(IPath)
+ */
+ public static void importPreferences(IPath path) throws CoreException {
+ if (!path.toFile().exists()) {
+ String msg = NLS.bind(PrefsMessages.preferences_fileNotFound, path.toOSString());
+ throw new CoreException(new Status(IStatus.ERROR, PrefsMessages.OWNER_NAME, 1, msg, null));
+ }
+ IPreferencesService service = PreferencesService.getDefault();
+ InputStream input = null;
+ try {
+ input = new BufferedInputStream(new FileInputStream(path.toFile()));
+ service.importPreferences(input);
+ } catch (FileNotFoundException e) {
+ String msg = NLS.bind(PrefsMessages.preferences_fileNotFound, path.toOSString());
+ throw new CoreException(new Status(IStatus.ERROR, PrefsMessages.OWNER_NAME, 1, msg, e));
+ } finally {
+ if (input != null)
+ try {
+ input.close();
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+ }
+
+ /**
+ * Validates that the preference versions in the given file match the versions
+ * of the currently installed plugins. Returns an OK status if all preferences match
+ * the currently installed plugins, otherwise a MultiStatus describing what
+ * plugins have preferences that don't match.
+ * <p>
+ * If the returned status has a <code>IStatus.WARNING</code> severity,
+ * it means that some preferences may not be applicable but for the most
+ * part they will be compatible. If the returned status has a
+ * <code>IStatus.ERROR</code> severity, it means that the preferences
+ * will probably not be compatible.
+ * <p>
+ * If the file contains preferences for plug-ins that don't exist in the current
+ * install, they are ignored.
+ * </p>
+ * <p>
+ * The file must have been written by the exportPreferences method.
+ * </p>
+ * @param file The absolute file system path of the preference file to validate.
+ * @see #exportPreferences(IPath)
+ * @see #importPreferences(IPath)
+ */
+ public static IStatus validatePreferenceVersions(IPath file) {
+ PreferencesService service = PreferencesService.getDefault();
+ return service.validateVersions(file);
+ }
+
+ /**
+ * Creates an empty preference table.
+ * <p>
+ * Use the methods <code>load(InputStream)</code> and
+ * <code>store(InputStream)</code> to load and store these preferences.
+ * </p>
+ * @see #load(InputStream)
+ * @see #store(OutputStream, String)
+ */
+ public Preferences() {
+ defaultProperties = new Properties();
+ properties = new Properties(defaultProperties);
+ }
+
+ /**
+ * Adds a property change listener to this preference object.
+ * Has no affect if the identical listener is already registered.
+ * <p>
+ * <em>Note:</em> Depending on the means in which the property
+ * values changed, the old and new values for the property can
+ * be either typed, a string representation of the value, or <code>null</code>.
+ * Clients who wish to behave properly in all cases should all
+ * three cases in their implementation of the property change listener.
+ * </p>
+ * @param listener a property change listener
+ */
+ public void addPropertyChangeListener(IPropertyChangeListener listener) {
+ listeners.add(listener);
+ }
+
+ /**
+ * Removes the given listener from this preference object.
+ * Has no affect if the listener is not registered.
+ *
+ * @param listener a property change listener
+ */
+ public void removePropertyChangeListener(IPropertyChangeListener listener) {
+ listeners.remove(listener);
+ }
+
+ /**
+ * Returns whether the given property is known to this preference object,
+ * either by having an explicit setting or by having a default
+ * setting. Returns <code>false</code> if the given name is <code>null</code>.
+ *
+ * @param name the name of the property, or <code>null</code>
+ * @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) {
+ return (properties.containsKey(name) || defaultProperties.containsKey(name));
+ }
+
+ /**
+ * Fires a property change event corresponding to a change to the
+ * current value of the property with the given name.
+ *
+ * @param name the name of the property, to be used as the property
+ * in the event object
+ * @param oldValue the old value, or <code>null</code> if not known or not
+ * relevant
+ * @param newValue the new value, or <code>null</code> if not known or not
+ * relevant
+ */
+ protected void firePropertyChangeEvent(String name, Object oldValue, Object newValue) {
+ if (name == null)
+ throw new IllegalArgumentException();
+ Object[] changeListeners = this.listeners.getListeners();
+ // Do we even need to fire an event?
+ if (changeListeners.length == 0)
+ return;
+ final PropertyChangeEvent pe = new PropertyChangeEvent(this, name, oldValue, newValue);
+ for (int i = 0; i < changeListeners.length; ++i) {
+ final IPropertyChangeListener l = (IPropertyChangeListener) changeListeners[i];
+ ISafeRunnable job = new ISafeRunnable() {
+ public void handleException(Throwable exception) {
+ // already being logged in Platform#run()
+ }
+
+ public void run() throws Exception {
+ l.propertyChange(pe);
+ }
+ };
+ SafeRunner.run(job);
+ }
+ }
+
+ /**
+ * 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.
+ * The given name must not be <code>null</code>.
+ *
+ * @param name the name of the property
+ * @return the boolean-valued property
+ */
+ public boolean getBoolean(String name) {
+ String value = properties.getProperty(name);
+ if (value == null) {
+ return BOOLEAN_DEFAULT_DEFAULT;
+ }
+ return value.equals(Preferences.TRUE);
+ }
+
+ /**
+ * Sets the current value of the boolean-valued property with the
+ * given name. The given name must not be <code>null</code>.
+ * <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 defaultValue = getDefaultBoolean(name);
+ boolean oldValue = getBoolean(name);
+ if (value == defaultValue) {
+ Object removed = properties.remove(name);
+ if (removed != null) {
+ // removed an explicit setting
+ dirty = true;
+ }
+ } else {
+ properties.put(name, value ? Preferences.TRUE : Preferences.FALSE);
+ }
+ if (oldValue != value) {
+ // mark as dirty since value did really change
+ dirty = true;
+ // report property change if getValue now returns different value
+ firePropertyChangeEvent(name, oldValue ? Boolean.TRUE : Boolean.FALSE, value ? Boolean.TRUE : Boolean.FALSE);
+ }
+ }
+
+ /**
+ * 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.
+ * The given name must not be <code>null</code>.
+ *
+ * @param name the name of the property
+ * @return the default value of the named property
+ */
+ public boolean getDefaultBoolean(String name) {
+ String value = defaultProperties.getProperty(name);
+ if (value == null) {
+ return BOOLEAN_DEFAULT_DEFAULT;
+ }
+ return value.equals(Preferences.TRUE);
+ }
+
+ /**
+ * Sets the default value for the boolean-valued property with the
+ * given name. The given name must not be <code>null</code>.
+ * <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) {
+ defaultProperties.put(name, value ? Preferences.TRUE : Preferences.FALSE);
+ }
+
+ /**
+ * 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.
+ * The given name must not be <code>null</code>.
+ *
+ * @param name the name of the property
+ * @return the double-valued property
+ */
+ public double getDouble(String name) {
+ return convertToDouble(properties.getProperty(name), DOUBLE_DEFAULT_DEFAULT);
+ }
+
+ /**
+ * Sets the current value of the double-valued property with the
+ * given name. The given name must not be <code>null</code>.
+ * <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();
+ }
+ double defaultValue = getDefaultDouble(name);
+ double oldValue = getDouble(name);
+ if (value == defaultValue) {
+ Object removed = properties.remove(name);
+ if (removed != null) {
+ // removed an explicit setting
+ dirty = true;
+ }
+ } else {
+ properties.put(name, Double.toString(value));
+ }
+ if (oldValue != value) {
+ // mark as dirty since value did really change
+ dirty = true;
+ // report property change if getValue now returns different value
+ firePropertyChangeEvent(name, new Double(oldValue), new Double(value));
+ }
+ }
+
+ /**
+ * 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.
+ * The given name must not be <code>null</code>.
+ *
+ * @param name the name of the property
+ * @return the default value of the named property
+ */
+ public double getDefaultDouble(String name) {
+ return convertToDouble(defaultProperties.getProperty(name), DOUBLE_DEFAULT_DEFAULT);
+ }
+
+ /**
+ * Sets the default value for the double-valued property with the
+ * given name. The given name must not be <code>null</code>.
+ * <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();
+ }
+ defaultProperties.put(name, Double.toString(value));
+ }
+
+ /**
+ * Converts the given raw property value string to a double.
+ *
+ * @param rawPropertyValue the raw property value, or <code>null</code>
+ * if none
+ * @param defaultValue the default value
+ * @return the raw value converted to a double, or the given
+ * <code>defaultValue</code> if the raw value is <code>null</code> or
+ * cannot be parsed as a double
+ */
+ private double convertToDouble(String rawPropertyValue, double defaultValue) {
+ double result = defaultValue;
+ if (rawPropertyValue != null) {
+ try {
+ result = Double.parseDouble(rawPropertyValue);
+ } catch (NumberFormatException e) {
+ // raw value cannot be treated as one of these
+ }
+ }
+ return result;
+ }
+
+ /**
+ * 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.
+ * The given name must not be <code>null</code>.
+ *
+ * @param name the name of the property
+ * @return the float-valued property
+ */
+ public float getFloat(String name) {
+ return convertToFloat(properties.getProperty(name), FLOAT_DEFAULT_DEFAULT);
+ }
+
+ /**
+ * Sets the current value of the float-valued property with the
+ * given name. The given name must not be <code>null</code>.
+ * <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();
+ }
+ float defaultValue = getDefaultFloat(name);
+ float oldValue = getFloat(name);
+ if (value == defaultValue) {
+ Object removed = properties.remove(name);
+ if (removed != null) {
+ // removed an explicit setting
+ dirty = true;
+ }
+ } else {
+ properties.put(name, Float.toString(value));
+ }
+ if (oldValue != value) {
+ // mark as dirty since value did really change
+ dirty = true;
+ // report property change if getValue now returns different value
+ firePropertyChangeEvent(name, new Float(oldValue), new Float(value));
+ }
+ }
+
+ /**
+ * 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.
+ * The given name must not be <code>null</code>.
+ *
+ * @param name the name of the property
+ * @return the default value of the named property
+ */
+ public float getDefaultFloat(String name) {
+ return convertToFloat(defaultProperties.getProperty(name), FLOAT_DEFAULT_DEFAULT);
+ }
+
+ /**
+ * Sets the default value for the float-valued property with the
+ * given name. The given name must not be <code>null</code>.
+ * <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();
+ }
+ defaultProperties.put(name, Float.toString(value));
+ }
+
+ /**
+ * Converts the given raw property value string to a float.
+ *
+ * @param rawPropertyValue the raw property value, or <code>null</code>
+ * if none
+ * @param defaultValue the default value
+ * @return the raw value converted to a float, or the given
+ * <code>defaultValue</code> if the raw value is <code>null</code> or
+ * cannot be parsed as a float
+ */
+ private float convertToFloat(String rawPropertyValue, float defaultValue) {
+ float result = defaultValue;
+ if (rawPropertyValue != null) {
+ try {
+ result = Float.parseFloat(rawPropertyValue);
+ } catch (NumberFormatException e) {
+ // raw value cannot be treated as one of these
+ }
+ }
+ return result;
+ }
+
+ /**
+ * 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 integer.
+ * The given name must not be <code>null</code>.
+ *
+ * @param name the name of the property
+ * @return the int-valued property
+ */
+ public int getInt(String name) {
+ return convertToInt(properties.getProperty(name), INT_DEFAULT_DEFAULT);
+ }
+
+ /**
+ * Sets the current value of the integer-valued property with the
+ * given name. The given name must not be <code>null</code>.
+ * <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) {
+ int defaultValue = getDefaultInt(name);
+ int oldValue = getInt(name);
+ if (value == defaultValue) {
+ Object removed = properties.remove(name);
+ if (removed != null) {
+ // removed an explicit setting
+ dirty = true;
+ }
+ } else {
+ properties.put(name, Integer.toString(value));
+ }
+ if (oldValue != value) {
+ // mark as dirty since value did really change
+ dirty = true;
+ // report property change if getValue now returns different value
+ firePropertyChangeEvent(name, new Integer(oldValue), new Integer(value));
+ }
+ }
+
+ /**
+ * 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.
+ * The given name must not be <code>null</code>.
+ *
+ * @param name the name of the property
+ * @return the default value of the named property
+ */
+ public int getDefaultInt(String name) {
+ return convertToInt(defaultProperties.getProperty(name), INT_DEFAULT_DEFAULT);
+ }
+
+ /**
+ * Sets the default value for the integer-valued property with the
+ * given name. The given name must not be <code>null</code>.
+ * <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) {
+ defaultProperties.put(name, Integer.toString(value));
+ }
+
+ /**
+ * Converts the given raw property value string to an int.
+ *
+ * @param rawPropertyValue the raw property value, or <code>null</code>
+ * if none
+ * @param defaultValue the default value
+ * @return the raw value converted to an int, or the given
+ * <code>defaultValue</code> if the raw value is <code>null</code> or
+ * cannot be parsed as an int
+ */
+ private int convertToInt(String rawPropertyValue, int defaultValue) {
+ int result = defaultValue;
+ if (rawPropertyValue != null) {
+ try {
+ result = Integer.parseInt(rawPropertyValue);
+ } catch (NumberFormatException e) {
+ // raw value cannot be treated as one of these
+ }
+ }
+ return result;
+ }
+
+ /**
+ * 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.
+ * The given name must not be <code>null</code>.
+ *
+ * @param name the name of the property
+ * @return the long-valued property
+ */
+ public long getLong(String name) {
+ return convertToLong(properties.getProperty(name), LONG_DEFAULT_DEFAULT);
+ }
+
+ /**
+ * Sets the current value of the long-valued property with the
+ * given name. The given name must not be <code>null</code>.
+ * <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) {
+ long defaultValue = getDefaultLong(name);
+ long oldValue = getLong(name);
+ if (value == defaultValue) {
+ Object removed = properties.remove(name);
+ if (removed != null) {
+ // removed an explicit setting
+ dirty = true;
+ }
+ } else {
+ properties.put(name, Long.toString(value));
+ }
+ if (oldValue != value) {
+ // mark as dirty since value did really change
+ dirty = true;
+ // report property change if getValue now returns different value
+ firePropertyChangeEvent(name, new Long(oldValue), new Long(value));
+ }
+ }
+
+ /**
+ * 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.
+ * The given name must not be <code>null</code>.
+ *
+ * @param name the name of the property
+ * @return the default value of the named property
+ */
+ public long getDefaultLong(String name) {
+ return convertToLong(defaultProperties.getProperty(name), LONG_DEFAULT_DEFAULT);
+ }
+
+ /**
+ * Sets the default value for the long-valued property with the
+ * given name. The given name must not be <code>null</code>.
+ * <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) {
+ defaultProperties.put(name, Long.toString(value));
+ }
+
+ /**
+ * Converts the given raw property value string to a long.
+ *
+ * @param rawPropertyValue the raw property value, or <code>null</code>
+ * if none
+ * @param defaultValue the default value
+ * @return the raw value converted to a long, or the given
+ * <code>defaultValue</code> if the raw value is <code>null</code> or
+ * cannot be parsed as a long
+ */
+ private long convertToLong(String rawPropertyValue, long defaultValue) {
+ long result = defaultValue;
+ if (rawPropertyValue != null) {
+ try {
+ result = Long.parseLong(rawPropertyValue);
+ } catch (NumberFormatException e) {
+ // raw value cannot be treated as one of these
+ }
+ }
+ return result;
+ }
+
+ /**
+ * 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.
+ * The given name must not be <code>null</code>.
+ *
+ * @param name the name of the property
+ * @return the string-valued property
+ */
+ public String getString(String name) {
+ String value = properties.getProperty(name);
+ return (value != null ? value : STRING_DEFAULT_DEFAULT);
+ }
+
+ /**
+ * Sets the current value of the string-valued property with the
+ * given name. The given name must not be <code>null</code>.
+ * <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 defaultValue = getDefaultString(name);
+ String oldValue = getString(name);
+ if (value.equals(defaultValue)) {
+ Object removed = properties.remove(name);
+ if (removed != null) {
+ // removed an explicit setting
+ dirty = true;
+ }
+ } else {
+ properties.put(name, value);
+ }
+ if (!oldValue.equals(value)) {
+ // mark as dirty since value did really change
+ dirty = true;
+ // report property change if getValue now returns different value
+ firePropertyChangeEvent(name, oldValue, value);
+ }
+ }
+
+ /**
+ * 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.
+ * The given name must not be <code>null</code>.
+ *
+ * @param name the name of the property
+ * @return the default value of the named property
+ */
+ public String getDefaultString(String name) {
+ String value = defaultProperties.getProperty(name);
+ return (value != null ? value : STRING_DEFAULT_DEFAULT);
+ }
+
+ /**
+ * Sets the default value for the string-valued property with the
+ * given name. The given name must not be <code>null</code>.
+ * <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();
+ }
+ defaultProperties.put(name, value);
+ }
+
+ /**
+ * Returns whether the property with the given name has the default value in
+ * virtue of having no explicitly set value.
+ * Returns <code>false</code> if the given name is <code>null</code>.
+ *
+ * @param name the name of the property, or <code>null</code>
+ * @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) {
+ return !properties.containsKey(name);
+ }
+
+ /**
+ * 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. The given name must not be <code>null</code>.
+ * <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) {
+ Object oldPropertyValue = properties.remove(name);
+ if (oldPropertyValue != null) {
+ dirty = true;
+ }
+ String newValue = defaultProperties.getProperty(name, null);
+ // n.b. newValue == null if there is no default value
+ // can't determine correct default-default without knowing type
+ firePropertyChangeEvent(name, oldPropertyValue, newValue);
+ }
+
+ /**
+ * 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 (String[]) properties.keySet().toArray(EMPTY_STRING_ARRAY);
+ }
+
+ /**
+ * Returns a list of all properties known to this preference object which
+ * have an explicit default value set.
+ *
+ * @return an array of property names
+ */
+ public String[] defaultPropertyNames() {
+ return (String[]) defaultProperties.keySet().toArray(EMPTY_STRING_ARRAY);
+ }
+
+ /**
+ * 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 dirty;
+ }
+
+ /**
+ * Saves the non-default-valued properties known to this preference object to
+ * the given output stream using
+ * <code>Properties.store(OutputStream,String)</code>.
+ * <p>
+ * Note that the output is unconditionally written, even when
+ * <code>needsSaving</code> is <code>false</code>.
+ * </p>
+ *
+ * @param out the output stream
+ * @param header a comment to be included in the output, or
+ * <code>null</code> if none
+ * @exception IOException if there is a problem saving this preference object
+ * @see Properties#store(OutputStream,String)
+ */
+ public void store(OutputStream out, String header) throws IOException {
+ properties.store(out, header);
+ dirty = false;
+ }
+
+ /**
+ * Loads the non-default-valued properties for this preference object from the
+ * given input stream using
+ * <code>java.util.Properties.load(InputStream)</code>. Default property
+ * values are not affected.
+ *
+ * @param in the input stream
+ * @exception IOException if there is a problem loading this preference
+ * object
+ * @see java.util.Properties#load(InputStream)
+ */
+ public void load(InputStream in) throws IOException {
+ properties.load(in);
+ dirty = false;
+ }
+}
diff --git a/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/runtime/preferences/AbstractPreferenceInitializer.java b/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/runtime/preferences/AbstractPreferenceInitializer.java
new file mode 100644
index 0000000..5387407
--- /dev/null
+++ b/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/runtime/preferences/AbstractPreferenceInitializer.java
@@ -0,0 +1,52 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2005 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.runtime.preferences;
+
+/**
+ * Abstract class used to aid in default preference value initialization.
+ * Clients who extend the <code>org.eclipse.core.runtime.preferences</code>
+ * extension point are able to specify a class within an <code>initializer</code>
+ * element.
+ *
+ * @since 3.0
+ */
+public abstract class AbstractPreferenceInitializer {
+
+ /**
+ * Default constructor for the class.
+ */
+ public AbstractPreferenceInitializer() {
+ super();
+ }
+
+ /**
+ * This method is called by the preference initializer to initialize default
+ * preference values. Clients should get the correct node for their
+ * bundle and then set the default values on it. For example:
+ * <pre>
+ * public void initializeDefaultPreferences() {
+ * Preferences node = new DefaultScope().getNode("my.bundle.id");
+ * node.put(key, value);
+ * }
+ * </pre>
+ * <p>
+ * <em>Note: Clients should only set default preference values for their
+ * own bundle.</em>
+ * </p>
+ * <p>
+ * <em>Note:</em> Clients should not call this method. It will be called
+ * automatically by the preference initializer when the appropriate default
+ * preference node is accessed.
+ * </p>
+ */
+ public abstract void initializeDefaultPreferences();
+
+}
diff --git a/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/runtime/preferences/ConfigurationScope.java b/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/runtime/preferences/ConfigurationScope.java
new file mode 100644
index 0000000..096b3fb
--- /dev/null
+++ b/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/runtime/preferences/ConfigurationScope.java
@@ -0,0 +1,79 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2005 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.runtime.preferences;
+
+import java.net.URL;
+import org.eclipse.core.internal.preferences.AbstractScope;
+import org.eclipse.core.internal.preferences.PreferencesOSGiUtils;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.osgi.service.datalocation.Location;
+
+/**
+ * Object representing the configuration scope in the Eclipse preferences
+ * hierarchy. Can be used as a context for searching for preference
+ * values (in the IPreferencesService APIs) or for determining the
+ * correct preference node to set values in the store.
+ * <p>
+ * Configuration preferences are stored on a per configuration basis in the
+ * platform's configuration area. (The configuration area typically
+ * contains the list of plug-ins available for use, various settings
+ * (those shared across different instances of the same configuration)
+ * and any other such data needed by plug-ins.)
+ * </p>
+ * <p>
+ * The path for preferences defined in the configuration scope hierarchy
+ * is as follows: <code>/configuration/<qualifier></code>
+ * </p>
+ * <p>
+ * This class is not intended to be subclassed. This class may be instantiated.
+ * </p>
+ * @since 3.0
+ */
+public final class ConfigurationScope extends AbstractScope {
+
+ /**
+ * String constant (value of <code>"configuration"</code>) used for the
+ * scope name for the configuration preference scope.
+ */
+ public static final String SCOPE = "configuration"; //$NON-NLS-1$
+
+ /**
+ * Create and return a new configuration scope instance.
+ */
+ public ConfigurationScope() {
+ super();
+ }
+
+ /*
+ * @see org.eclipse.core.runtime.preferences.IScopeContext#getName()
+ */
+ public String getName() {
+ return SCOPE;
+ }
+
+ /*
+ * @see org.eclipse.core.runtime.preferences.IScopeContext#getLocation()
+ */
+ public IPath getLocation() {
+ IPath result = null;
+ Location location = PreferencesOSGiUtils.getDefault().getConfigurationLocation();
+ if (!location.isReadOnly()) {
+ URL url = location.getURL();
+ if (url != null) {
+ result = new Path(url.getFile());
+ if (result.isEmpty())
+ result = null;
+ }
+ }
+ return result;
+ }
+}
diff --git a/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/runtime/preferences/DefaultScope.java b/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/runtime/preferences/DefaultScope.java
new file mode 100644
index 0000000..925c266
--- /dev/null
+++ b/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/runtime/preferences/DefaultScope.java
@@ -0,0 +1,70 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2005 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.runtime.preferences;
+
+import org.eclipse.core.internal.preferences.AbstractScope;
+import org.eclipse.core.runtime.IPath;
+
+/**
+ * Object representing the default scope in the Eclipse preferences
+ * hierarchy. Can be used as a context for searching for preference
+ * values (in the IPreferencesService APIs) or for determining the
+ * correct preference node to set values in the store.
+ * <p>
+ * Default preferences are not persisted to disk.
+ * </p>
+ * <p>
+ * The path for preferences defined in the default scope hierarchy
+ * is as follows: <code>/default/<qualifier></code>
+ * </p>
+ * <p>
+ * Note about product preference customization:
+ * Clients who define their own {@link org.eclipse.core.runtime.IProduct}
+ * are able to specify a product key of "<code>preferenceCustomization</code>".
+ * (defined as a constant in {@link org.eclipse.ui.branding.IProductConstants})
+ * Its value is either a {@link java.net.URL} or a file-system path to a
+ * file whose contents are used to customize default preferences.
+ * </p>
+ * <p>
+ * This class is not intended to be subclassed. This class may be instantiated.
+ * </p>
+ * @since 3.0
+ */
+public final class DefaultScope extends AbstractScope {
+
+ /**
+ * String constant (value of <code>"default"</code>) used for the
+ * scope name for the default preference scope.
+ */
+ public static final String SCOPE = "default"; //$NON-NLS-1$
+
+ /**
+ * Create and return a new default scope instance.
+ */
+ public DefaultScope() {
+ super();
+ }
+
+ /*
+ * @see org.eclipse.core.runtime.preferences.IScopeContext#getName()
+ */
+ public String getName() {
+ return SCOPE;
+ }
+
+ /*
+ * @see org.eclipse.core.runtime.preferences.IScopeContext#getLocation()
+ */
+ public IPath getLocation() {
+ // We don't persist defaults so return null.
+ return null;
+ }
+}
diff --git a/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/runtime/preferences/IEclipsePreferences.java b/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/runtime/preferences/IEclipsePreferences.java
new file mode 100644
index 0000000..a5d49ca
--- /dev/null
+++ b/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/runtime/preferences/IEclipsePreferences.java
@@ -0,0 +1,316 @@
+/*******************************************************************************
+ * 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.runtime.preferences;
+
+import java.util.EventObject;
+import org.osgi.service.prefs.BackingStoreException;
+import org.osgi.service.prefs.Preferences;
+
+/**
+ * This interface describes Eclipse extensions to the preference
+ * story. It provides means for both preference and node change
+ * listeners.
+ * <p>
+ * Clients may implement this interface.
+ * </p>
+ *
+ * @see org.osgi.service.prefs.Preferences
+ * @since 3.0
+ */
+public interface IEclipsePreferences extends Preferences {
+
+ /**
+ * An event object which describes the details of a change in the
+ * preference node hierarchy. The child node is the one which
+ * was added or removed.
+ *
+ * @see IEclipsePreferences.INodeChangeListener
+ * @since 3.0
+ */
+ public final class NodeChangeEvent extends EventObject {
+ /**
+ * All serializable objects should have a stable serialVersionUID
+ */
+ private static final long serialVersionUID = 1L;
+
+ private Preferences child;
+
+ /**
+ * Constructor for a new node change event object.
+ *
+ * @param parent the parent node
+ * @param child the child node
+ */
+ public NodeChangeEvent(Preferences parent, Preferences child) {
+ super(parent);
+ this.child = child;
+ }
+
+ /**
+ * Return the parent node for this event. This is the parent
+ * of the node which was added or removed.
+ *
+ * @return the parent node
+ */
+ public Preferences getParent() {
+ return (Preferences) getSource();
+ }
+
+ /**
+ * Return the child node for this event. This is the node
+ * which was added or removed.
+ * <p>
+ * Note: The child node may have been removed as a result of
+ * the bundle supplying its implementation being un-installed. In this case
+ * the only method which can safely be called on the child is #name().
+ * </p>
+ * @return the child node
+ */
+ public Preferences getChild() {
+ return child;
+ }
+ }
+
+ /**
+ * A listener to be used to receive preference node change events.
+ * <p>
+ * Clients may implement this interface.
+ * </p>
+ *
+ * @since 3.0
+ */
+ public interface INodeChangeListener {
+
+ /**
+ * Notification that a child node was added to the preference hierarchy.
+ * The given event must not be <code>null</code>.
+ *
+ * @param event an event specifying the details about the new node
+ * @see IEclipsePreferences.NodeChangeEvent
+ * @see IEclipsePreferences#addNodeChangeListener(IEclipsePreferences.INodeChangeListener)
+ * @see IEclipsePreferences#removeNodeChangeListener(IEclipsePreferences.INodeChangeListener)
+ */
+ public void added(NodeChangeEvent event);
+
+ /**
+ * Notification that a child node was removed from the preference hierarchy.
+ * The given event must not be <code>null</code>.
+ *
+ * @param event an event specifying the details about the removed node
+ * @see IEclipsePreferences.NodeChangeEvent
+ * @see IEclipsePreferences#addNodeChangeListener(IEclipsePreferences.INodeChangeListener)
+ * @see IEclipsePreferences#removeNodeChangeListener(IEclipsePreferences.INodeChangeListener)
+ */
+ public void removed(NodeChangeEvent event);
+ }
+
+ /**
+ * An event object describing the details of a change to a preference
+ * in the preference store.
+ *
+ * @see IEclipsePreferences.IPreferenceChangeListener
+ * @since 3.0
+ */
+ public final class PreferenceChangeEvent extends EventObject {
+ /**
+ * All serializable objects should have a stable serialVersionUID
+ */
+ private static final long serialVersionUID = 1L;
+
+ private String key;
+ private Object newValue;
+ private Object oldValue;
+
+ /**
+ * Constructor for a new preference change event. The node and the
+ * key must not be <code>null</code>. The old and new preference
+ * values must be either a <code>String</code> or <code>null</code>.
+ *
+ * @param node the node on which the change occurred
+ * @param key the preference key
+ * @param oldValue the old preference value, as a <code>String</code>
+ * or <code>null</code>
+ * @param newValue the new preference value, as a <code>String</code>
+ * or <code>null</code>
+ */
+ public PreferenceChangeEvent(Object node, String key, Object oldValue, Object newValue) {
+ super(node);
+ if (key == null || !(node instanceof Preferences))
+ throw new IllegalArgumentException();
+ this.key = key;
+ this.newValue = newValue;
+ this.oldValue = oldValue;
+ }
+
+ /**
+ * Return the preference node on which the change occurred.
+ * Must not be <code>null</code>.
+ *
+ * @return the node
+ */
+ public Preferences getNode() {
+ return (Preferences) source;
+ }
+
+ /**
+ * Return the key of the preference which was changed.
+ * Must not be <code>null</code>.
+ *
+ * @return the preference key
+ */
+ public String getKey() {
+ return key;
+ }
+
+ /**
+ * Return the new value for the preference encoded as a
+ * <code>String</code>, or <code>null</code> if the
+ * preference was removed.
+ *
+ * @return the new value or <code>null</code>
+ */
+ public Object getNewValue() {
+ return newValue;
+ }
+
+ /**
+ * Return the old value for the preference encoded as a
+ * <code>String</code>, or <code>null</code> if the
+ * preference was removed or if it cannot be determined.
+ *
+ * @return the old value or <code>null</code>
+ */
+ public Object getOldValue() {
+ return oldValue;
+ }
+ }
+
+ /**
+ * A listener used to receive changes to preference values in the preference store.
+ * <p>
+ * Clients may implement this interface.
+ * </p>
+ *
+ * @since 3.0
+ */
+ public interface IPreferenceChangeListener {
+
+ /**
+ * Notification that a preference value has changed in the preference store.
+ * The given event object describes the change details and must not
+ * be <code>null</code>.
+ *
+ * @param event the event details
+ * @see IEclipsePreferences.PreferenceChangeEvent
+ * @see IEclipsePreferences#addPreferenceChangeListener(IEclipsePreferences.IPreferenceChangeListener)
+ * @see IEclipsePreferences#removePreferenceChangeListener(IEclipsePreferences.IPreferenceChangeListener)
+ */
+ public void preferenceChange(PreferenceChangeEvent event);
+ }
+
+ /**
+ * Register the given listener for changes to this node. Duplicate calls
+ * to this method with the same listener will have no effect. The given
+ * listener argument must not be <code>null</code>.
+ *
+ * @param listener the node change listener to add
+ * @throws IllegalStateException if this node or an ancestor has been removed
+ * @see #removeNodeChangeListener(IEclipsePreferences.INodeChangeListener)
+ * @see IEclipsePreferences.INodeChangeListener
+ */
+ public void addNodeChangeListener(INodeChangeListener listener);
+
+ /**
+ * De-register the given listener from receiving event change notifications
+ * for this node. Calling this method with a listener which is not registered
+ * has no effect. The given listener argument must not be <code>null</code>.
+ *
+ * @param listener the node change listener to remove
+ * @throws IllegalStateException if this node or an ancestor has been removed
+ * @see #addNodeChangeListener(IEclipsePreferences.INodeChangeListener)
+ * @see IEclipsePreferences.INodeChangeListener
+ */
+ public void removeNodeChangeListener(INodeChangeListener listener);
+
+ /**
+ * Register the given listener for notification of preference changes to this node.
+ * Calling this method multiple times with the same listener has no effect. The
+ * given listener argument must not be <code>null</code>.
+ *
+ * @param listener the preference change listener to register
+ * @throws IllegalStateException if this node or an ancestor has been removed
+ * @see #removePreferenceChangeListener(IEclipsePreferences.IPreferenceChangeListener)
+ * @see IEclipsePreferences.IPreferenceChangeListener
+ */
+ public void addPreferenceChangeListener(IPreferenceChangeListener listener);
+
+ /**
+ * De-register the given listener from receiving notification of preference changes
+ * to this node. Calling this method multiple times with the same listener has no
+ * effect. The given listener argument must not be <code>null</code>.
+ *
+ * @param listener the preference change listener to remove
+ * @throws IllegalStateException if this node or an ancestor has been removed
+ * @see #addPreferenceChangeListener(IEclipsePreferences.IPreferenceChangeListener)
+ * @see IEclipsePreferences.IPreferenceChangeListener
+ */
+ public void removePreferenceChangeListener(IPreferenceChangeListener listener);
+
+ /**
+ * Remove this node from the preference hierarchy. If this node is the scope
+ * root, then do not remove this node, only remove this node's children.
+ * <p>
+ * Functionally equivalent to calling {@link Preferences#removeNode()}.
+ * See the spec of {@link Preferences#removeNode()} for more details.
+ * </p>
+ * <p>
+ * Implementors must send the appropriate {@link NodeChangeEvent}
+ * to listeners who are registered on this node's parent.
+ * </p>
+ * <p>
+ * When this node is removed, its associated preference and node change
+ * listeners should be removed as well.
+ * </p>
+ * @throws BackingStoreException if there was a problem removing this node
+ * @see org.osgi.service.prefs.Preferences#removeNode()
+ * @see NodeChangeEvent
+ */
+ public void removeNode() throws BackingStoreException;
+
+ /**
+ * Return the preferences node with the given path. The given path must
+ * not be <code>null</code>.
+ * <p>
+ * See the spec of {@link Preferences#node(String)} for more details.
+ * </p>
+ * <p>
+ * Note that if the node does not yet exist and is created, then the appropriate
+ * {@link NodeChangeEvent} must be sent to listeners who are
+ * registered at this node.
+ * </p>
+ * @param path the path of the node
+ * @return the node
+ * @see org.osgi.service.prefs.Preferences#node(String)
+ * @see NodeChangeEvent
+ */
+ public Preferences node(String path);
+
+ /**
+ * Accepts the given visitor. The visitor's <code>visit</code> method
+ * is called with this node. If the visitor returns <code>true</code>,
+ * this method visits this node's children.
+ *
+ * @param visitor the visitor
+ * @see IPreferenceNodeVisitor#visit(IEclipsePreferences)
+ * @throws BackingStoreException
+ */
+ public void accept(IPreferenceNodeVisitor visitor) throws BackingStoreException;
+}
diff --git a/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/runtime/preferences/IExportedPreferences.java b/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/runtime/preferences/IExportedPreferences.java
new file mode 100644
index 0000000..70ad2de
--- /dev/null
+++ b/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/runtime/preferences/IExportedPreferences.java
@@ -0,0 +1,34 @@
+/*******************************************************************************
+ * 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.runtime.preferences;
+
+/**
+ * Represents a node in the preference hierarchy which is used in
+ * the import/export mechanism.
+ * <p>
+ * This interface is not intended to be implemented by clients.
+ * </p>
+ * @since 3.0
+ */
+public interface IExportedPreferences extends IEclipsePreferences {
+
+ /**
+ * Return <code>true</code> if this node was an export root
+ * when the preferences were exported, and <code>false</code>
+ * otherwise. This information is used during the import to clear
+ * nodes when importing a node's (and its children's) preferences.
+ *
+ * @return <code>true</code> if this node is an export root
+ * and <code>false</code> otherwise
+ */
+ public boolean isExportRoot();
+
+}
diff --git a/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/runtime/preferences/IPreferenceFilter.java b/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/runtime/preferences/IPreferenceFilter.java
new file mode 100644
index 0000000..ff44b1c
--- /dev/null
+++ b/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/runtime/preferences/IPreferenceFilter.java
@@ -0,0 +1,77 @@
+/*******************************************************************************
+ * Copyright (c) 2005 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.runtime.preferences;
+
+import java.util.Map;
+
+/**
+ * Preference filters are used to describe the relationship between the
+ * preference tree and a data set when importing/exporting preferences.
+ * <p>
+ * For instance, a client is able to create a preference filter describing
+ * which preference nodes/keys should be used when exporting the
+ * "Key Bindings" preferences. When the export happens, the tree is
+ * trimmed and only the applicable preferences will be exported.
+ * </p>
+ * <p>
+ * Clients may implement this interface.
+ * </p>
+ *
+ * @since 3.1
+ */
+public interface IPreferenceFilter {
+
+ /**
+ * Return an array of scopes that this preference filter is applicable for. The list
+ * of scopes must not be <code>null</code>.
+ * <p>
+ * For example:
+ * <code>new String[] {InstanceScope.SCOPE, ConfigurationScope.SCOPE};</code>
+ * </p>
+ *
+ * @return the array of scopes
+ */
+ public String[] getScopes();
+
+ /**
+ * Return a mapping which defines the nodes and keys that this filter
+ * applies to.
+ * <p>
+ * If the map is <code>null</code> then this filter is applicable for all
+ * nodes within the scope. The map can also be <code>null</code> if
+ * the given scope is not known to this filter.
+ * </p>
+ * <p>
+ * The keys in the table are Strings and describe the node path. The values are
+ * an optional array of {@link PreferenceFilterEntry} objects describing the list of
+ * applicable keys in that node. If the value is null then the whole node is
+ * considered applicable.
+ * </p>
+ * <p>
+ * key: <code>String</code> (node)<br>
+ * value: <code>PreferenceFilterEntry[]</code> or <code>null</code> (preference keys)<br>
+ * </p>
+ * <p>
+ * For example:
+ * <pre>
+ * "org.eclipse.core.resources" -> null
+ * "org.eclipse.ui" -> new PreferenceFilterEntry[] {
+ * new PreferenceFilterEntry("DEFAULT_PERSPECTIVE_LOCATION"),
+ * new PreferenceFilterEntry("SHOW_INTRO_ON_STARTUP")}
+ * </pre>
+ * </p>
+ *
+ * @return the mapping table
+ * @see PreferenceFilterEntry
+ */
+ public Map getMapping(String scope);
+
+}
diff --git a/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/runtime/preferences/IPreferenceNodeVisitor.java b/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/runtime/preferences/IPreferenceNodeVisitor.java
new file mode 100644
index 0000000..7d2a61f
--- /dev/null
+++ b/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/runtime/preferences/IPreferenceNodeVisitor.java
@@ -0,0 +1,47 @@
+/*******************************************************************************
+ * 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.runtime.preferences;
+
+import org.osgi.service.prefs.BackingStoreException;
+
+/**
+ * This interface is implemented by objects that visit preference nodes.
+ * <p>
+ * Usage:
+ * <pre>
+ * class Visitor implements IPreferenceNodeVisitor {
+ * public boolean visit(IEclipsePreferences node) {
+ * // your code here
+ * return true;
+ * }
+ * }
+ * IEclipsePreferences root = ...;
+ * root.accept(new Visitor());
+ * </pre>
+ * </p><p>
+ * Clients may implement this interface.
+ * </p>
+ *
+ * @see IEclipsePreferences#accept(IPreferenceNodeVisitor)
+ * @since 3.0
+ */
+public interface IPreferenceNodeVisitor {
+
+ /**
+ * Visits the given preference node.
+ *
+ * @param node the node to visit
+ * @return <code>true</code> if the node's children should
+ * be visited; <code>false</code> if they should be skipped
+ * @throws BackingStoreException
+ */
+ public boolean visit(IEclipsePreferences node) throws BackingStoreException;
+}
diff --git a/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/runtime/preferences/IPreferencesService.java b/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/runtime/preferences/IPreferencesService.java
new file mode 100644
index 0000000..1428700
--- /dev/null
+++ b/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/runtime/preferences/IPreferencesService.java
@@ -0,0 +1,618 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2005 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.runtime.preferences;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IStatus;
+import org.osgi.service.prefs.Preferences;
+
+/**
+ * The preference service provides facilities for dealing with the default scope
+ * precedence lookup order, querying the preference store for values using this order,
+ * accessing the root of the preference store node hierarchy, and importing/exporting
+ * preferences.
+ * <p>
+ * The default-default preference search look-up order as defined by the platform
+ * is: project, instance, configuration, default.
+ * </p><p>
+ * This interface is not intended to be implemented by clients.
+ * </p>
+ * @since 3.0
+ */
+public interface IPreferencesService {
+
+ /**
+ * Lookup the given key in the specified preference nodes in the given order.
+ * Return the value from the first node the key is found in. If the key is not
+ * defined in any of the given nodes, then return the specified default value.
+ * <p>
+ * Immediately returns the default value if the node list is <code>null</code>.
+ * If any of the individual entries in the node list are <code>null</code> then
+ * skip over them and move on to the next node in the list.
+ * </p>
+ * @param key the preference key
+ * @param defaultValue the default value
+ * @param nodes the list of nodes to search, or <code>null</code>
+ * @return the stored preference value or the specified default value
+ * @see org.osgi.service.prefs.Preferences
+ */
+ public String get(String key, String defaultValue, Preferences[] nodes);
+
+ /**
+ * Return the value stored in the preference store for the given key.
+ * If the key is not defined then return the specified default value.
+ * Use the canonical scope lookup order for finding the preference value.
+ * <p>
+ * The semantics of this method are to calculate the appropriate
+ * {@link Preferences} nodes in the preference hierarchy to use
+ * and then call the {@link #get(String, String, Preferences[])}
+ * method. The order of the nodes is calculated by consulting the default
+ * scope lookup order as set by {@link #setDefaultLookupOrder(String, String, String[])}.
+ * </p><p>
+ * The specified key may either refer to a simple key or be the concatenation of the
+ * path of a child node and key. If the key contains a slash ("/") character, then a
+ * double-slash must be used to denote the end of they child path and the beginning
+ * of the key. Otherwise it is assumed that the key is the last segment of the path.
+ * The following are some examples of keys and their meanings:
+ * <ul>
+ * <li>"a" - look for a value for the property "a"
+ * <li>"//a" - look for a value for the property "a"
+ * <li>"///a" - look for a value for the property "/a"
+ * <li>"//a//b" - look for a value for the property "a//b"
+ * <li>"a/b/c" - look in the child node "a/b" for property "c"
+ * <li>"/a/b/c" - look in the child node "a/b" for property "c"
+ * <li>"/a/b//c" - look in the child node "a/b" for the property "c"
+ * <li>"a/b//c/d" - look in the child node "a/b" for the property "c/d"
+ * <li>"/a/b//c/d" - look in the child node "a/b&qu