Skip to main content
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJeff McAffer2005-11-21 23:08:47 +0000
committerJeff McAffer2005-11-21 23:08:47 +0000
commit45009ada3d70007a38c2b01760ee3990156cf0f3 (patch)
tree20f72908f5e790cd9c5a25a8962a75770991f07a
downloadrt.equinox.bundles-45009ada3d70007a38c2b01760ee3990156cf0f3.tar.gz
rt.equinox.bundles-45009ada3d70007a38c2b01760ee3990156cf0f3.tar.xz
rt.equinox.bundles-45009ada3d70007a38c2b01760ee3990156cf0f3.zip
Bug 113663 [plan item] Refactor the runtime. Initail commitv200511211912
-rw-r--r--bundles/org.eclipse.equinox.common/.classpath7
-rw-r--r--bundles/org.eclipse.equinox.common/.cvsignore1
-rw-r--r--bundles/org.eclipse.equinox.common/.project28
-rw-r--r--bundles/org.eclipse.equinox.common/META-INF/MANIFEST.MF12
-rw-r--r--bundles/org.eclipse.equinox.common/about.html24
-rw-r--r--bundles/org.eclipse.equinox.common/build.properties17
-rw-r--r--bundles/org.eclipse.equinox.common/plugin.properties12
-rw-r--r--bundles/org.eclipse.equinox.common/src/org/eclipse/core/internal/runtime/Activator.java46
-rw-r--r--bundles/org.eclipse.equinox.common/src/org/eclipse/core/internal/runtime/AdapterManager.java353
-rw-r--r--bundles/org.eclipse.equinox.common/src/org/eclipse/core/internal/runtime/CommonMessages.java58
-rw-r--r--bundles/org.eclipse.equinox.common/src/org/eclipse/core/internal/runtime/CommonOSGiUtils.java180
-rw-r--r--bundles/org.eclipse.equinox.common/src/org/eclipse/core/internal/runtime/DataArea.java126
-rw-r--r--bundles/org.eclipse.equinox.common/src/org/eclipse/core/internal/runtime/DevClassPathHelper.java98
-rw-r--r--bundles/org.eclipse.equinox.common/src/org/eclipse/core/internal/runtime/FindSupport.java246
-rw-r--r--bundles/org.eclipse.equinox.common/src/org/eclipse/core/internal/runtime/IAdapterFactoryExt.java31
-rw-r--r--bundles/org.eclipse.equinox.common/src/org/eclipse/core/internal/runtime/IRuntimeConstants.java37
-rw-r--r--bundles/org.eclipse.equinox.common/src/org/eclipse/core/internal/runtime/ListenerList.java160
-rw-r--r--bundles/org.eclipse.equinox.common/src/org/eclipse/core/internal/runtime/MessageResourceBundle.java225
-rw-r--r--bundles/org.eclipse.equinox.common/src/org/eclipse/core/internal/runtime/MetaDataKeeper.java37
-rw-r--r--bundles/org.eclipse.equinox.common/src/org/eclipse/core/internal/runtime/ReferenceHashSet.java323
-rw-r--r--bundles/org.eclipse.equinox.common/src/org/eclipse/core/internal/runtime/ResourceTranslator.java134
-rw-r--r--bundles/org.eclipse.equinox.common/src/org/eclipse/core/internal/runtime/RuntimeLog.java93
-rw-r--r--bundles/org.eclipse.equinox.common/src/org/eclipse/core/internal/runtime/commonMessages.properties42
-rw-r--r--bundles/org.eclipse.equinox.common/src/org/eclipse/core/internal/runtime/package.html24
-rw-r--r--bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/Assert.java110
-rw-r--r--bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/AssertionFailedException.java28
-rw-r--r--bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/BundleFinder.java134
-rw-r--r--bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/CoreException.java98
-rw-r--r--bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/IAdaptable.java47
-rw-r--r--bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/IAdapterFactory.java53
-rw-r--r--bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/IAdapterManager.java222
-rw-r--r--bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/ILogListener.java33
-rw-r--r--bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/IPath.java504
-rw-r--r--bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/IProgressMonitor.java128
-rw-r--r--bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/IProgressMonitorWithBlocking.java60
-rw-r--r--bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/ISafeRunnable.java48
-rw-r--r--bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/IStatus.java184
-rw-r--r--bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/ListenerList.java169
-rw-r--r--bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/MultiStatus.java146
-rw-r--r--bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/NullProgressMonitor.java124
-rw-r--r--bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/OperationCanceledException.java42
-rw-r--r--bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/Path.java986
-rw-r--r--bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/PlatformObject.java67
-rw-r--r--bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/PluginVersionIdentifier.java485
-rw-r--r--bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/ProgressMonitorWrapper.java168
-rw-r--r--bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/QualifiedName.java114
-rw-r--r--bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/SafeRunner.java67
-rw-r--r--bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/Status.java231
-rw-r--r--bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/SubProgressMonitor.java177
-rw-r--r--bundles/org.eclipse.equinox.preferences/.classpath7
-rw-r--r--bundles/org.eclipse.equinox.preferences/.cvsignore1
-rw-r--r--bundles/org.eclipse.equinox.preferences/.options6
-rw-r--r--bundles/org.eclipse.equinox.preferences/.project28
-rw-r--r--bundles/org.eclipse.equinox.preferences/META-INF/MANIFEST.MF16
-rw-r--r--bundles/org.eclipse.equinox.preferences/about.html60
-rw-r--r--bundles/org.eclipse.equinox.preferences/build.properties19
-rw-r--r--bundles/org.eclipse.equinox.preferences/plugin.properties14
-rw-r--r--bundles/org.eclipse.equinox.preferences/plugin.xml11
-rw-r--r--bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/AbstractScope.java67
-rw-r--r--bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/Activator.java88
-rw-r--r--bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/Base64.java198
-rw-r--r--bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/ConfigurationPreferences.java118
-rw-r--r--bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/DefaultPreferences.java360
-rw-r--r--bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/EclipsePreferences.java1163
-rw-r--r--bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/ExportedPreferences.java78
-rw-r--r--bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/ILegacyPreferences.java29
-rw-r--r--bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/IPreferencesConstants.java33
-rw-r--r--bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/ImmutableMap.java279
-rw-r--r--bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/InstancePreferences.java211
-rw-r--r--bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/ListenerRegistry.java159
-rw-r--r--bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/LookupOrder.java33
-rw-r--r--bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/PreferenceForwarder.java851
-rw-r--r--bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/PreferencesOSGiUtils.java196
-rw-r--r--bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/PreferencesService.java1154
-rw-r--r--bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/PrefsMessages.java69
-rw-r--r--bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/RootPreferences.java129
-rw-r--r--bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/StringPool.java68
-rw-r--r--bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/internal/preferences/messages.properties34
-rw-r--r--bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/runtime/Preferences.java1284
-rw-r--r--bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/runtime/preferences/AbstractPreferenceInitializer.java52
-rw-r--r--bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/runtime/preferences/ConfigurationScope.java79
-rw-r--r--bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/runtime/preferences/DefaultScope.java70
-rw-r--r--bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/runtime/preferences/IEclipsePreferences.java316
-rw-r--r--bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/runtime/preferences/IExportedPreferences.java34
-rw-r--r--bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/runtime/preferences/IPreferenceFilter.java77
-rw-r--r--bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/runtime/preferences/IPreferenceNodeVisitor.java47
-rw-r--r--bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/runtime/preferences/IPreferencesService.java618
-rw-r--r--bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/runtime/preferences/IProductPreferencesService.java30
-rw-r--r--bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/runtime/preferences/IScope.java39
-rw-r--r--bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/runtime/preferences/IScopeContext.java70
-rw-r--r--bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/runtime/preferences/InstanceScope.java64
-rw-r--r--bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/runtime/preferences/PreferenceFilterEntry.java47
-rw-r--r--bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/runtime/preferences/PreferenceModifyListener.java49
-rw-r--r--bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/runtime/preferences/package.html17
-rw-r--r--bundles/org.eclipse.equinox.preferences/src/org/osgi/service/prefs/BackingStoreException.java74
-rw-r--r--bundles/org.eclipse.equinox.preferences/src/org/osgi/service/prefs/Preferences.java694
-rw-r--r--bundles/org.eclipse.equinox.preferences/src/org/osgi/service/prefs/PreferencesService.java48
-rw-r--r--bundles/org.eclipse.equinox.registry/.classpath7
-rw-r--r--bundles/org.eclipse.equinox.registry/.cvsignore1
-rw-r--r--bundles/org.eclipse.equinox.registry/.options4
-rw-r--r--bundles/org.eclipse.equinox.registry/.project28
-rw-r--r--bundles/org.eclipse.equinox.registry/META-INF/MANIFEST.MF18
-rw-r--r--bundles/org.eclipse.equinox.registry/about.html51
-rw-r--r--bundles/org.eclipse.equinox.registry/about_files/NOTICE.txt2
-rw-r--r--bundles/org.eclipse.equinox.registry/about_files/asl-v20.txt202
-rw-r--r--bundles/org.eclipse.equinox.registry/build.properties20
-rw-r--r--bundles/org.eclipse.equinox.registry/plugin.properties12
-rw-r--r--bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/ConfigurationElement.java280
-rw-r--r--bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/ConfigurationElementHandle.java128
-rw-r--r--bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/Contribution.java97
-rw-r--r--bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/Extension.java120
-rw-r--r--bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/ExtensionDelta.java70
-rw-r--r--bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/ExtensionHandle.java67
-rw-r--r--bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/ExtensionPoint.java130
-rw-r--r--bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/ExtensionPointHandle.java94
-rw-r--r--bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/ExtensionRegistry.java1101
-rw-r--r--bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/ExtensionsParser.java592
-rw-r--r--bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/Handle.java51
-rw-r--r--bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/HashtableOfInt.java153
-rw-r--r--bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/HashtableOfStringAndInt.java212
-rw-r--r--bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/ICompatibilityStrategy.java59
-rw-r--r--bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/IObjectManager.java26
-rw-r--r--bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/IRegistryConstants.java39
-rw-r--r--bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/KeyedElement.java19
-rw-r--r--bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/KeyedHashSet.java316
-rw-r--r--bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/ReadWriteMonitor.java105
-rw-r--r--bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/ReferenceMap.java407
-rw-r--r--bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/RegistryChangeEvent.java87
-rw-r--r--bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/RegistryDelta.java77
-rw-r--r--bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/RegistryMessages.java77
-rw-r--r--bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/RegistryObject.java62
-rw-r--r--bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/RegistryObjectFactory.java60
-rw-r--r--bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/RegistryObjectManager.java560
-rw-r--r--bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/RegistrySupport.java76
-rw-r--r--bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/TableReader.java554
-rw-r--r--bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/TableWriter.java314
-rw-r--r--bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/TemporaryObjectManager.java137
-rw-r--r--bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/ThirdLevelConfigurationElementHandle.java32
-rw-r--r--bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/messages.properties57
-rw-r--r--bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/osgi/Activator.java71
-rw-r--r--bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/osgi/EclipseBundleListener.java184
-rw-r--r--bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/osgi/EquinoxRegistryStrategy.java76
-rw-r--r--bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/osgi/OSGIUtils.java187
-rw-r--r--bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/osgi/RegistryStrategyOSGI.java334
-rw-r--r--bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/spi/ExtensionDescription.java110
-rw-r--r--bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/spi/ExtensionProperty.java58
-rw-r--r--bundles/org.eclipse.equinox.registry/src/org/eclipse/core/runtime/InvalidRegistryObjectException.java40
-rw-r--r--bundles/org.eclipse.equinox.registry/src/org/eclipse/equinox/registry/IConfigurationElement.java296
-rw-r--r--bundles/org.eclipse.equinox.registry/src/org/eclipse/equinox/registry/IExecutableExtension.java120
-rw-r--r--bundles/org.eclipse.equinox.registry/src/org/eclipse/equinox/registry/IExecutableExtensionFactory.java47
-rw-r--r--bundles/org.eclipse.equinox.registry/src/org/eclipse/equinox/registry/IExtension.java132
-rw-r--r--bundles/org.eclipse.equinox.registry/src/org/eclipse/equinox/registry/IExtensionDelta.java57
-rw-r--r--bundles/org.eclipse.equinox.registry/src/org/eclipse/equinox/registry/IExtensionPoint.java156
-rw-r--r--bundles/org.eclipse.equinox.registry/src/org/eclipse/equinox/registry/IExtensionRegistry.java285
-rw-r--r--bundles/org.eclipse.equinox.registry/src/org/eclipse/equinox/registry/IRegistryChangeEvent.java62
-rw-r--r--bundles/org.eclipse.equinox.registry/src/org/eclipse/equinox/registry/IRegistryChangeListener.java43
-rw-r--r--bundles/org.eclipse.equinox.registry/src/org/eclipse/equinox/registry/RegistryFactory.java56
-rw-r--r--bundles/org.eclipse.equinox.registry/src/org/eclipse/equinox/registry/spi/RegistryStrategy.java377
-rw-r--r--bundles/org.eclipse.equinox.registry/src/org/eclipse/equinox/registry/tracker/ExtensionTracker.java303
-rw-r--r--bundles/org.eclipse.equinox.registry/src/org/eclipse/equinox/registry/tracker/IExtensionChangeHandler.java38
-rw-r--r--bundles/org.eclipse.equinox.registry/src/org/eclipse/equinox/registry/tracker/IExtensionTracker.java99
-rw-r--r--bundles/org.eclipse.equinox.registry/src/org/eclipse/equinox/registry/tracker/IFilter.java31
-rw-r--r--bundles/org.eclipse.equinox.registry/src/org/eclipse/equinox/registry/tracker/package.html14
163 files changed, 25607 insertions, 0 deletions
diff --git a/bundles/org.eclipse.equinox.common/.classpath b/bundles/org.eclipse.equinox.common/.classpath
new file mode 100644
index 000000000..751c8f2e5
--- /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 000000000..ba077a403
--- /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 000000000..2289dee11
--- /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 000000000..d7bfc7457
--- /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 000000000..cdc1e5004
--- /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 (&quot;Content&quot;). Unless otherwise indicated below, the Content is provided to you under the terms and conditions of the
+Eclipse Public License Version 1.0 (&quot;EPL&quot;). 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, &quot;Program&quot; 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 (&quot;Redistributor&quot;) 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 000000000..c1e8ad782
--- /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 000000000..9badd2f46
--- /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 000000000..324f169fc
--- /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 000000000..55e9a0213
--- /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 000000000..af00d3d77
--- /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 000000000..b4aae6b15
--- /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 000000000..6eaf5f6bd
--- /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 000000000..cfab2ea6b
--- /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 000000000..1a9b83519
--- /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 000000000..c4739803d
--- /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 000000000..a31af5b56
--- /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 000000000..3b0e49e03
--- /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 000000000..4f7564e30
--- /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 000000000..164f318a3
--- /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 000000000..eab95b961
--- /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 000000000..f12aebcf2
--- /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 000000000..0d7be586b
--- /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 000000000..61c1a8100
--- /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 000000000..0898ea239
--- /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 000000000..1ecef9686
--- /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 000000000..8f3cbcb92
--- /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 000000000..810759e92
--- /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 000000000..9ac5b61d7
--- /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 000000000..a935fadfa
--- /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 000000000..994eaae3b
--- /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 000000000..330fe5c62
--- /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(&quot;com.example.acme&quot;, &quot;sticky-note&quot;);
+ * 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 000000000..b8c454823
--- /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 000000000..d9eb8ca38
--- /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 000000000..3a8f2d758
--- /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 000000000..23a2bac47
--- /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 000000000..02a7ce10a
--- /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 000000000..a2418858e
--- /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 000000000..f5c833a87
--- /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 000000000..5d8ed507c
--- /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 000000000..6c54d475a
--- /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 000000000..c0137351b
--- /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 000000000..6eb25cacb
--- /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 000000000..5b2e5c872
--- /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 000000000..737574ac4
--- /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 000000000..5b7117c61
--- /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 000000000..893faa797
--- /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 000000000..90f9db308
--- /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 000000000..70a36422e
--- /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 000000000..425771005
--- /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 000000000..751c8f2e5
--- /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 000000000..ba077a403
--- /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 000000000..c676c7c7d
--- /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 000000000..0ccb37523
--- /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 000000000..ac1f9ed3f
--- /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 000000000..bd87495af
--- /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 (&quot;Content&quot;). Unless otherwise indicated below, the Content is provided to you under the terms and conditions of the
+Eclipse Public License Version 1.0 (&quot;EPL&quot;). 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, &quot;Program&quot; 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 (&quot;Redistributor&quot;) 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 &quot;OSGi Materials.&quot; 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 (&quot;EPL&quot;). For purposes of the EPL, &quot;Program&quot; 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&trade; 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 000000000..b3000cca0
--- /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 000000000..85acdaaf3
--- /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 000000000..cfaffa88a
--- /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 000000000..6ffebd06d
--- /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 000000000..1735ddd1b
--- /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 000000000..de8518445
--- /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 000000000..72e1426cc
--- /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 000000000..cacf87cbc
--- /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 000000000..b7b4169e9
--- /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 000000000..38d6f26b5
--- /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 000000000..7c03e1853
--- /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 000000000..be4799d84
--- /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 000000000..183c6951b
--- /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 000000000..333f83ff5
--- /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 000000000..73570e935
--- /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 000000000..96284d865
--- /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 000000000..c0ee012ab
--- /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 000000000..922906476
--- /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 000000000..67dbb25fe
--- /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 000000000..87f34ddd0
--- /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 000000000..d660842e8
--- /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 000000000..5d361aa9f
--- /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 000000000..abfb6464e
--- /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 000000000..8d882ce97
--- /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 000000000..5387407a5
--- /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 000000000..096b3fba6
--- /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/&lt;qualifier&gt;</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 000000000..925c26670
--- /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/&lt;qualifier&gt;</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 000000000..a5d49caa4
--- /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 000000000..70ad2de98
--- /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 000000000..ff44b1c31
--- /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 000000000..7d2a61f12
--- /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 000000000..142870098
--- /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" for the property "c/d"
+ * <li>"/a/b//c//d" - look in the child node "a/b" for the property "c//d"
+ * </ul>
+ * </p><p>
+ * Callers may specify an array of scope context objects to aid in the
+ * determination of the correct nodes. For each entry in the lookup
+ * order, the array of contexts is consulted and if one matching the
+ * scope exists, then it is used to calculate the node. Otherwise a
+ * default calculation algorithm is used.
+ * </p><p>
+ * An example of a qualifier for an Eclipse 2.1 preference is the
+ * plug-in identifier. (e.g. "org.eclipse.core.resources" for "description.autobuild")
+ * </p>
+ * @param qualifier a namespace qualifier for the preference
+ * @param key the name of the preference (optionally including its path)
+ * @param defaultValue the value to use if the preference is not defined
+ * @param contexts optional context objects to help scopes determine which nodes to search, or <code>null</code>
+ * @return the value of the preference or the given default value
+ * @see IScopeContext
+ * @see #get(java.lang.String, java.lang.String, org.osgi.service.prefs.Preferences[])
+ * @see #getLookupOrder(java.lang.String, java.lang.String)
+ * @see #getDefaultLookupOrder(java.lang.String, java.lang.String)
+ */
+ public boolean getBoolean(String qualifier, String key, boolean defaultValue, IScopeContext[] contexts);
+
+ /**
+ * 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" for the property "c/d"
+ * <li>"/a/b//c//d" - look in the child node "a/b" for the property "c//d"
+ * </ul>
+ * </p><p>
+ * Callers may specify an array of scope context objects to aid in the
+ * determination of the correct nodes. For each entry in the lookup
+ * order, the array of contexts is consulted and if one matching the
+ * scope exists, then it is used to calculate the node. Otherwise a
+ * default calculation algorithm is used.
+ * </p><p>
+ * An example of a qualifier for an Eclipse 2.1 preference is the
+ * plug-in identifier. (e.g. "org.eclipse.core.resources" for "description.autobuild")
+ * </p>
+ * @param qualifier a namespace qualifier for the preference
+ * @param key the name of the preference (optionally including its path)
+ * @param defaultValue the value to use if the preference is not defined
+ * @param contexts optional context objects to help scopes determine which nodes to search, or <code>null</code>
+ * @return the value of the preference or the given default value
+ * @see IScopeContext
+ * @see #get(java.lang.String, java.lang.String, org.osgi.service.prefs.Preferences[])
+ * @see #getLookupOrder(java.lang.String, java.lang.String)
+ * @see #getDefaultLookupOrder(java.lang.String, java.lang.String)
+ */
+ public byte[] getByteArray(String qualifier, String key, byte[] defaultValue, IScopeContext[] contexts);
+
+ /**
+ * 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" for the property "c/d"
+ * <li>"/a/b//c//d" - look in the child node "a/b" for the property "c//d"
+ * </ul>
+ * </p><p>
+ * Callers may specify an array of scope context objects to aid in the
+ * determination of the correct nodes. For each entry in the lookup
+ * order, the array of contexts is consulted and if one matching the
+ * scope exists, then it is used to calculate the node. Otherwise a
+ * default calculation algorithm is used.
+ * </p><p>
+ * An example of a qualifier for an Eclipse 2.1 preference is the
+ * plug-in identifier. (e.g. "org.eclipse.core.resources" for "description.autobuild")
+ * </p>
+ * @param qualifier a namespace qualifier for the preference
+ * @param key the name of the preference (optionally including its path)
+ * @param defaultValue the value to use if the preference is not defined
+ * @param contexts optional context objects to help scopes determine which nodes to search, or <code>null</code>
+ * @return the value of the preference or the given default value
+ * @see IScopeContext
+ * @see #get(java.lang.String, java.lang.String, org.osgi.service.prefs.Preferences[])
+ * @see #getLookupOrder(java.lang.String, java.lang.String)
+ * @see #getDefaultLookupOrder(java.lang.String, java.lang.String)
+ */
+ public double getDouble(String qualifier, String key, double defaultValue, IScopeContext[] contexts);
+
+ /**
+ * 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" for the property "c/d"
+ * <li>"/a/b//c//d" - look in the child node "a/b" for the property "c//d"
+ * </ul>
+ * </p><p>
+ * Callers may specify an array of scope context objects to aid in the
+ * determination of the correct nodes. For each entry in the lookup
+ * order, the array of contexts is consulted and if one matching the
+ * scope exists, then it is used to calculate the node. Otherwise a
+ * default calculation algorithm is used.
+ * </p><p>
+ * An example of a qualifier for an Eclipse 2.1 preference is the
+ * plug-in identifier. (e.g. "org.eclipse.core.resources" for "description.autobuild")
+ * </p>
+ * @param qualifier a namespace qualifier for the preference
+ * @param key the name of the preference (optionally including its path)
+ * @param defaultValue the value to use if the preference is not defined
+ * @param contexts optional context objects to help scopes determine which nodes to search, or <code>null</code>
+ * @return the value of the preference or the given default value
+ * @see IScopeContext
+ * @see #get(java.lang.String, java.lang.String, org.osgi.service.prefs.Preferences[])
+ * @see #getLookupOrder(java.lang.String, java.lang.String)
+ * @see #getDefaultLookupOrder(java.lang.String, java.lang.String)
+ */
+ public float getFloat(String qualifier, String key, float defaultValue, IScopeContext[] contexts);
+
+ /**
+ * 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" for the property "c/d"
+ * <li>"/a/b//c//d" - look in the child node "a/b" for the property "c//d"
+ * </ul>
+ * </p><p>
+ * Callers may specify an array of scope context objects to aid in the
+ * determination of the correct nodes. For each entry in the lookup
+ * order, the array of contexts is consulted and if one matching the
+ * scope exists, then it is used to calculate the node. Otherwise a
+ * default calculation algorithm is used.
+ * </p><p>
+ * An example of a qualifier for an Eclipse 2.1 preference is the
+ * plug-in identifier. (e.g. "org.eclipse.core.resources" for "description.autobuild")
+ * </p>
+ * @param qualifier a namespace qualifier for the preference
+ * @param key the name of the preference (optionally including its path)
+ * @param defaultValue the value to use if the preference is not defined
+ * @param contexts optional context objects to help scopes determine which nodes to search, or <code>null</code>
+ * @return the value of the preference or the given default value
+ * @see IScopeContext
+ * @see #get(java.lang.String, java.lang.String, org.osgi.service.prefs.Preferences[])
+ * @see #getLookupOrder(java.lang.String, java.lang.String)
+ * @see #getDefaultLookupOrder(java.lang.String, java.lang.String)
+ */
+ public int getInt(String qualifier, String key, int defaultValue, IScopeContext[] contexts);
+
+ /**
+ * 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" for the property "c/d"
+ * <li>"/a/b//c//d" - look in the child node "a/b" for the property "c//d"
+ * </ul>
+ * </p><p>
+ * Callers may specify an array of scope context objects to aid in the
+ * determination of the correct nodes. For each entry in the lookup
+ * order, the array of contexts is consulted and if one matching the
+ * scope exists, then it is used to calculate the node. Otherwise a
+ * default calculation algorithm is used.
+ * </p><p>
+ * An example of a qualifier for an Eclipse 2.1 preference is the
+ * plug-in identifier. (e.g. "org.eclipse.core.resources" for "description.autobuild")
+ * </p>
+ * @param qualifier a namespace qualifier for the preference
+ * @param key the name of the preference (optionally including its path)
+ * @param defaultValue the value to use if the preference is not defined
+ * @param contexts optional context objects to help scopes determine which nodes to search, or <code>null</code>
+ * @return the value of the preference or the given default value
+ * @see IScopeContext
+ * @see #get(java.lang.String, java.lang.String, org.osgi.service.prefs.Preferences[])
+ * @see #getLookupOrder(java.lang.String, java.lang.String)
+ * @see #getDefaultLookupOrder(java.lang.String, java.lang.String)
+ */
+ public long getLong(String qualifier, String key, long defaultValue, IScopeContext[] contexts);
+
+ /**
+ * 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" for the property "c/d"
+ * <li>"/a/b//c//d" - look in the child node "a/b" for the property "c//d"
+ * </ul>
+ * </p><p>
+ * Callers may specify an array of scope context objects to aid in the
+ * determination of the correct nodes. For each entry in the lookup
+ * order, the array of contexts is consulted and if one matching the
+ * scope exists, then it is used to calculate the node. Otherwise a
+ * default calculation algorithm is used.
+ * </p><p>
+ * An example of a qualifier for an Eclipse 2.1 preference is the
+ * plug-in identifier. (e.g. "org.eclipse.core.resources" for "description.autobuild")
+ * </p>
+ * @param qualifier a namespace qualifier for the preference
+ * @param key the name of the preference (optionally including its path)
+ * @param defaultValue the value to use if the preference is not defined
+ * @param contexts optional context objects to help scopes determine which nodes to search, or <code>null</code>
+ * @return the value of the preference or the given default value
+ * @see IScopeContext
+ * @see #get(java.lang.String, java.lang.String, org.osgi.service.prefs.Preferences[])
+ * @see #getLookupOrder(java.lang.String, java.lang.String)
+ * @see #getDefaultLookupOrder(java.lang.String, java.lang.String)
+ */
+ public String getString(String qualifier, String key, String defaultValue, IScopeContext[] contexts);
+
+ /**
+ * Return the root node of the Eclipse preference hierarchy.
+ *
+ * @return the root of the hierarchy
+ */
+ public IEclipsePreferences getRootNode();
+
+ /**
+ * Exports all preferences for the given preference node and all its children to the specified
+ * output stream. It is the responsibility of the client to close the given output stream.
+ * <p>
+ * If the given export list is <code>null</code> then all preferences for all sub-nodes
+ * of the given node are exported to the given stream. Otherwise the export list is
+ * consulted before exporting each preference value. If there is a string match then
+ * the preference is not exported. The exclusion can also occur at a per-node level.
+ * Wild cards are <em>not</em> accepted in the excludes list as a basic String compare
+ * is done. The basic algorithm is similar to the following:
+ * <pre>
+ * String fullPath = node.absolutePath() + '/' + key;
+ * if (!fullPath.startsWith(excludesList[i]))
+ * // export preference
+ * </pre>
+ * </p>
+ * <p>
+ * The values stored in the resulting stream are suitable for later being read by the
+ * by {@link #importPreferences(InputStream)} or {@link #readPreferences(InputStream)} methods.
+ * </p>
+ * @param node the node to treat as the root of the export
+ * @param output the stream to write to
+ * @param excludesList a list of path prefixes to exclude from the export, or <code>null</code>
+ * @return a status object describing success or detailing failure reasons
+ * @throws CoreException if there was a problem exporting the preferences
+ * @throws IllegalArgumentException if the node or stream is <code>null</code>
+ * @see #importPreferences(java.io.InputStream)
+ * @see #readPreferences(InputStream)
+ */
+ public IStatus exportPreferences(IEclipsePreferences node, OutputStream output, String[] excludesList) throws CoreException;
+
+ /**
+ * Loads preferences from the given file and stores them in the preferences store.
+ * Existing values are over-ridden by those from the stream. The stream must not be
+ * <code>null</code> and is closed upon return from this method.
+ * <p>
+ * This file must have been written by the
+ * {@link #exportPreferences(IEclipsePreferences, OutputStream, String[])}
+ * method.
+ * </p>
+ * <p>
+ * This method is equivalent to calling <code>applyPreferences(readPreferences(input));</code>.
+ * </p>
+ * @param input the stream to load the preferences from
+ * @return a status object describing success or detailing failure reasons
+ * @throws CoreException if there are problems importing the preferences
+ * @throws IllegalArgumentException if the stream is <code>null</code>
+ * @see #exportPreferences(IEclipsePreferences, OutputStream, String[])
+ */
+ public IStatus importPreferences(InputStream input) throws CoreException;
+
+ /**
+ * Take the given preference tree and apply it to the Eclipse
+ * global preference hierarchy. If a node is an export root, then
+ * remove it from the global tree before adding any preferences
+ * contained in it or its children. The given preferences object
+ * must not be <code>null</code>.
+ * <p>
+ * Before the tree is applied to the global preference tree,
+ * the registered <code>PreferenceModifyListener</code> objects
+ * are called and given the opportunity to modify the tree.
+ * </p>
+ *
+ * @param preferences the preferences to apply globally
+ * @return status object indicating success or failure
+ * @throws IllegalArgumentException if the preferences are <code>null</code>
+ * @throws CoreException if there are problems applying the preferences
+ * @see PreferenceModifyListener
+ */
+ public IStatus applyPreferences(IExportedPreferences preferences) throws CoreException;
+
+ /**
+ * Read from the given input stream and create a node hierarchy
+ * representing the preferences and their values. The given input stream
+ * must not be <code>null</code>. The result of this function is suitable
+ * for passing as an argument to {@link #applyPreferences(IExportedPreferences)}.
+ * <p>
+ * It is assumed the contents of the input stream have been written by
+ * {@link #exportPreferences(IEclipsePreferences, OutputStream, String[])}.
+ * </p>
+ * @param input the input stream to read from
+ * @return the node hierarchy representing the stream contents
+ * @throws IllegalArgumentException if the given stream is null
+ * @throws CoreException if there are problems reading the preferences
+ * @see #exportPreferences(IEclipsePreferences, OutputStream, String[])
+ * @see #applyPreferences(IExportedPreferences)
+ */
+ public IExportedPreferences readPreferences(InputStream input) throws CoreException;
+
+ /**
+ * Return an array with the default lookup order for the preference keyed by the given
+ * qualifier and simple name. Return <code>null</code> if no default has been set.
+ * <p>
+ * The lookup order returned is based on an exact match to the specified qualifier
+ * and simple name. For instance, if the given key is non-<code>null</code> and
+ * no default lookup order is found, the default lookup order for the qualifier (and a
+ * <code>null</code> key) is <em>NOT</em> returned. Clients should call
+ * {@link #getLookupOrder(String, String)} if they desire this behavior.
+ * </p>
+ * @param qualifier the namespace qualifier for the preference
+ * @param key the preference name or <code>null</code>
+ * @return the scope order or <code>null</code>
+ * @see #setDefaultLookupOrder(String, String, String[])
+ * @see #getLookupOrder(String, String)
+ */
+ public String[] getDefaultLookupOrder(String qualifier, String key);
+
+ /**
+ * Return an array with the lookup order for the preference keyed by the given
+ * qualifier and simple name.
+ * <p>
+ * First do an exact match lookup with the given qualifier and simple name. If a match
+ * is found then return it. Otherwise if the key is non-<code>null</code> then
+ * do a lookup based on only the qualifier and return the set value.
+ * Return the default-default order as defined by the platform if no order has been set.
+ * </p>
+ * @param qualifier the namespace qualifier for the preference
+ * @param key the preference name or <code>null</code>
+ * @return the scope order
+ * @throws IllegalArgumentException if the qualifier is <code>null</code>
+ * @see #getDefaultLookupOrder(String, String)
+ * @see #setDefaultLookupOrder(String, String, String[])
+ */
+ public String[] getLookupOrder(String qualifier, String key);
+
+ /**
+ * Set the default scope lookup order for the preference keyed by the given
+ * qualifier and simple name. If the given order is <code>null</code> then the set
+ * ordering (if it exists) is removed.
+ * <p>
+ * If the given simple name is <code>null</code> then set the given lookup
+ * order to be used for all keys with the given qualifier.
+ * </p><p>
+ * Note that the default lookup order is not persisted across platform invocations.
+ * </p>
+ * @param qualifier the namespace qualifier for the preference
+ * @param key the preference name or <code>null</code>
+ * @param order the lookup order or <code>null</code>
+ * @throws IllegalArgumentException
+ * <ul>
+ * <li>if the qualifier is <code>null</code></li>
+ * <li>if an entry in the order array is <code>null</code> (the array itself is
+ * allowed to be <code>null</code></li>
+ * </ul>
+ * @see #getDefaultLookupOrder(String, String)
+ */
+ public void setDefaultLookupOrder(String qualifier, String key, String[] order);
+
+ /**
+ * Export the preference tree rooted at the given node, to the specified output
+ * stream. Apply the given list of preference filters, only exporting
+ * preference node and keys which are applicable to at least one filter in the list.
+ * <p>
+ * The given node and output stream must not be <code>null</code>.
+ * If the list of filters is <code>null</code> or empty then do nothing.
+ * </p>
+ * <p>
+ * It is the responsibility of the client to close the given output stream.
+ * </p>
+ *
+ * @param node the tree to export
+ * @param filters the list of filters to export
+ * @param output the stream to export to
+ * @throws CoreException
+ * @see #exportPreferences(IEclipsePreferences, OutputStream, String[])
+ * @see #readPreferences(InputStream)
+ * @see #applyPreferences(IEclipsePreferences, IPreferenceFilter[])
+ * @see #applyPreferences(IExportedPreferences)
+ * @see IPreferenceFilter
+ * @since 3.1
+ */
+ public void exportPreferences(IEclipsePreferences node, IPreferenceFilter[] filters, OutputStream output) throws CoreException;
+
+ /**
+ * Return a list of filters which match the given tree and is a subset of the given
+ * filter list. If the specified list of filters is <code>null</code>, empty, or there
+ * are no matches, then return an empty list.
+ *
+ * @param node the tree to match against
+ * @param filters the list of filters to match against
+ * @return the array of matching transfers
+ * @throws CoreException
+ * @see IPreferenceFilter
+ * @since 3.1
+ */
+ public IPreferenceFilter[] matches(IEclipsePreferences node, IPreferenceFilter[] filters) throws CoreException;
+
+ /**
+ * Apply the preference tree rooted at the given node, to the system's preference tree.
+ * The list of preference filters will act as a filter and only preferences in the tree which
+ * apply to at least one filter in the list, will be applied.
+ * <p>
+ * If the list of filters is <code>null</code> or empty then do nothing.
+ * </p>
+ * <p>
+ * Before the tree is applied to the global preference tree,
+ * the registered <code>PreferenceModifyListener</code> objects
+ * are called and given the opportunity to modify the tree.
+ * </p>
+ *
+ * @param node the tree to consider applying
+ * @param filters the filters to use
+ * @throws CoreException
+ * @see #applyPreferences(IExportedPreferences)
+ * @see #readPreferences(InputStream)
+ * @see IPreferenceFilter
+ * @see PreferenceModifyListener
+ * @since 3.1
+ */
+ public void applyPreferences(IEclipsePreferences node, IPreferenceFilter[] filters) throws CoreException;
+}
diff --git a/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/runtime/preferences/IProductPreferencesService.java b/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/runtime/preferences/IProductPreferencesService.java
new file mode 100644
index 000000000..be739dbf1
--- /dev/null
+++ b/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/runtime/preferences/IProductPreferencesService.java
@@ -0,0 +1,30 @@
+/*******************************************************************************
+ * 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.Properties;
+
+/**
+ * A product can customize preferences by implementing this service.
+ *
+ * @since org.eclipse.equinox.preferences 1.0
+ */
+public interface IProductPreferencesService {
+ /**
+ * @return default preferences specified by the product.
+ */
+ public Properties getProductCustomization();
+
+ /**
+ * @return translation table for default preferences
+ */
+ public Properties getProductTranslation();
+}
diff --git a/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/runtime/preferences/IScope.java b/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/runtime/preferences/IScope.java
new file mode 100644
index 000000000..29d3c1936
--- /dev/null
+++ b/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/runtime/preferences/IScope.java
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * 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;
+
+/**
+ * Clients contributing a scope to the Eclipse preference system must
+ * implement this interface to aid Eclipse in creating a new node for the
+ * hierarchy.
+ * <p>
+ * Clients may implement this interface.
+ * </p>
+ * @since 3.0
+ */
+public interface IScope {
+
+ /**
+ * Create and return a new preference node with the given parent and name.
+ * Must not return <code>null</code>. Clients are able to create a new node
+ * in memory or load the node's contents from the backing store. Neither the
+ * parent or name arguments should be <code>null</code>.
+ * <p>
+ * Implementors should note that the node might not have been added to the
+ * child list of the parent yet, and therefore might not be able to be referenced
+ * through navigation from the root node.
+ * </p>
+ * @param parent the node's parent
+ * @param name the name of the node
+ * @return the new node
+ */
+ public IEclipsePreferences create(IEclipsePreferences parent, String name);
+}
diff --git a/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/runtime/preferences/IScopeContext.java b/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/runtime/preferences/IScopeContext.java
new file mode 100644
index 000000000..f1fd7607f
--- /dev/null
+++ b/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/runtime/preferences/IScopeContext.java
@@ -0,0 +1,70 @@
+/*******************************************************************************
+ * 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.eclipse.core.runtime.IPath;
+
+/**
+ * Clients implement this interface to provide context to a
+ * particular scope. Instances of implementations of this interface are
+ * passed to the {@link IPreferencesService} for use in
+ * preference searching.
+ * <p>
+ * Clients may implement this interface.
+ * </p>
+ *
+ * @see IPreferencesService
+ * @since 3.0
+ */
+public interface IScopeContext {
+
+ /**
+ * Return the name of the scope that this context is associated with.
+ * Must not be <code>null</code>.
+ *
+ * @return the name of the scope
+ */
+ public String getName();
+
+ /**
+ * Return the preferences node that contains the preferences for the
+ * given qualifier or <code>null</code> if the node cannot be determined.
+ * The given qualifier must not be <code>null</code> but may be a path
+ * to a sub-node within the scope.
+ * <p>
+ * An example of a qualifier in Eclipse 2.1 would be the plug-in identifier that
+ * the preference is associated with (e.g. the "org.eclipse.core.resources"
+ * plug-in defines the "description.autobuild" preference).
+ * </p><p>
+ * This method can be used to determine the appropriate preferences node
+ * to aid in setting key/value pairs. For instance:
+ * <code>new InstanceScope().getNode("org.eclipse.core.resources");</code>
+ * returns the preference node in the instance scope where the preferences
+ * for "org.eclipse.core.resources" are stored.
+ * </p>
+ * @param qualifier a qualifier for the preference name
+ * @return the node containing the plug-in preferences or <code>null</code>
+ * @see IPreferencesService
+ */
+ public IEclipsePreferences getNode(String qualifier);
+
+ /**
+ * Return a path to a location in the file-system where clients are able
+ * to write files that will have the same sharing/scope properties as
+ * preferences defined in this scope.
+ * <p>
+ * Implementors may return <code>null</code> if the location is not known,
+ * is unavailable, or is not applicable to this scope.
+ * </p>
+ * @return a writable location in the file system or <code>null</code>
+ */
+ public IPath getLocation();
+}
diff --git a/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/runtime/preferences/InstanceScope.java b/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/runtime/preferences/InstanceScope.java
new file mode 100644
index 000000000..75612ec81
--- /dev/null
+++ b/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/runtime/preferences/InstanceScope.java
@@ -0,0 +1,64 @@
+/*******************************************************************************
+ * 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 instance 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>
+ * Instance preferences are stored on a per instance basis in the
+ * platform's instance area as specified by {@link org.eclipse.core.runtime.Platform#getInstanceLocation}.
+ * </p><p>
+ * The path for preferences defined in the instance scope hierarchy
+ * is as follows: <code>/instance/&lt;qualifier&gt;</code>
+ * </p>
+ * <p>
+ * This class is not intended to be subclassed. This class may be instantiated.
+ * </p>
+ * @see org.eclipse.core.runtime.Platform#getInstanceLocation()
+ * @since 3.0
+ */
+public final class InstanceScope extends AbstractScope {
+
+ /**
+ * String constant (value of <code>"instance"</code>) used for the
+ * scope name for the instance preference scope.
+ */
+ public static final String SCOPE = "instance"; //$NON-NLS-1$
+
+ /**
+ * Create and return a new instance scope instance.
+ */
+ public InstanceScope() {
+ super();
+ }
+
+ /*
+ * @see org.eclipse.core.runtime.preferences.IScopeContext#getLocation()
+ */
+ public IPath getLocation() {
+ // Return null. The instance location usually corresponds to the state
+ // location of the bundle and we don't know what bundle we are dealing with.
+ return null;
+ }
+
+ /*
+ * @see org.eclipse.core.runtime.preferences.IScopeContext#getName()
+ */
+ public String getName() {
+ return SCOPE;
+ }
+}
diff --git a/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/runtime/preferences/PreferenceFilterEntry.java b/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/runtime/preferences/PreferenceFilterEntry.java
new file mode 100644
index 000000000..dc6470f31
--- /dev/null
+++ b/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/runtime/preferences/PreferenceFilterEntry.java
@@ -0,0 +1,47 @@
+/*******************************************************************************
+ * 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;
+
+/**
+ * Class which represents and preference filter entry to be used during preference
+ * import/export (for example).
+ *
+ * @since 3.1
+ * @see org.eclipse.core.runtime.preferences.IPreferenceFilter
+ */
+public final class PreferenceFilterEntry {
+
+ private String key;
+
+ /**
+ * Constructor for the class. Create a new preference filter entry with the given
+ * key. The key must <em>not</em> be <code>null</code> or empty.
+ *
+ * @param key the name of the preference key
+ */
+ public PreferenceFilterEntry(String key) {
+ super();
+ if (key == null || key.length() == 0)
+ throw new IllegalArgumentException();
+ this.key = key;
+ }
+
+ /**
+ * Return the name of the preference key for this filter entry.
+ * It will <em>not</em> return <code>null</code> or the
+ * empty string.
+ *
+ * @return the name of the preference key
+ */
+ public String getKey() {
+ return key;
+ }
+}
diff --git a/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/runtime/preferences/PreferenceModifyListener.java b/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/runtime/preferences/PreferenceModifyListener.java
new file mode 100644
index 000000000..7149a3ac2
--- /dev/null
+++ b/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/runtime/preferences/PreferenceModifyListener.java
@@ -0,0 +1,49 @@
+/*******************************************************************************
+ * 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;
+
+/**
+ * This class provides a hook into the preference service before particular operations
+ * on the global preference tree. Preference modify listeners are registered with
+ * the preference service via XML and the <code>org.eclipse.core.runtime.preferences</code>
+ * extension point.
+ * <p>
+ * Clients may subclass this type.
+ * </p>
+ *
+ * @since 3.1
+ */
+public abstract class PreferenceModifyListener {
+
+ /**
+ * Clients are given the opportunity to modify the given tree before it is applied
+ * to the global preference tree. Clients should return the tree which should be
+ * applied globally. The tree passed in will not be <code>null</code> and clients
+ * <em>must not</em> return a <code>null</code> tree.
+ * <p>
+ * This method is called by the preference service from within calls to
+ * {@link IPreferencesService#applyPreferences(IExportedPreferences)} or
+ * {@link IPreferencesService#applyPreferences(IEclipsePreferences, IPreferenceFilter[])}.
+ * </p>
+ * <p>
+ * A typical action for clients to perform would be to intercept the incoming preference tree,
+ * migrate old preference values to new ones, and then return the new tree.
+ * </p>
+ *
+ * @param node the tree to modify
+ * @return the tree to apply to the global preferences
+ */
+ public IEclipsePreferences preApply(IEclipsePreferences node) {
+ // default implementation makes no modifications
+ return node;
+ }
+
+}
diff --git a/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/runtime/preferences/package.html b/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/runtime/preferences/package.html
new file mode 100644
index 000000000..7c88d888b
--- /dev/null
+++ b/bundles/org.eclipse.equinox.preferences/src/org/eclipse/core/runtime/preferences/package.html
@@ -0,0 +1,17 @@
+<!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 Eclipse preferences.
+<h2>
+Package Specification</h2>
+<p>
+This package specifies API for defining and accessing preferences in the runtime.
+<p>
+@since 3.0
+<p>
+</body>
+</html>
diff --git a/bundles/org.eclipse.equinox.preferences/src/org/osgi/service/prefs/BackingStoreException.java b/bundles/org.eclipse.equinox.preferences/src/org/osgi/service/prefs/BackingStoreException.java
new file mode 100644
index 000000000..cad818066
--- /dev/null
+++ b/bundles/org.eclipse.equinox.preferences/src/org/osgi/service/prefs/BackingStoreException.java
@@ -0,0 +1,74 @@
+/*
+ * $Header: /home/eclipse/org.eclipse.core.runtime/src/org/osgi/service/prefs/BackingStoreException.java,v 1.5 2005/11/11 22:07:19 johna Exp $
+ *
+ * Copyright (c) OSGi Alliance (2001, 2005). 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.
+ */
+package org.osgi.service.prefs;
+
+/**
+ * Thrown to indicate that a preferences operation could not complete because of
+ * a failure in the backing store, or a failure to contact the backing store.
+ *
+ * @version $Revision: 1.5 $
+ */
+public class BackingStoreException extends Exception {
+ static final long serialVersionUID = -1415637364122829574L;
+ /**
+ * Nested exception.
+ */
+ private Throwable cause;
+
+ /**
+ * Constructs a <code>BackingStoreException</code> with the specified detail
+ * message.
+ *
+ * @param s The detail message.
+ */
+ public BackingStoreException(String s) {
+ super(s);
+ }
+
+ /**
+ * Constructs a <code>BackingStoreException</code> with the specified detail
+ * message.
+ *
+ * @param s The detail message.
+ * @param cause The cause of the exception. May be <code>null</code>.
+ * @since 1.1
+ */
+ public BackingStoreException(String s, Throwable cause) {
+ super(s);
+ this.cause = cause;
+ }
+
+ /**
+ * Returns the cause of this exception or <code>null</code> if no cause was
+ * specified when this exception was created.
+ *
+ * @return The cause of this exception or <code>null</code> if no cause was
+ * specified.
+ * @since 1.1
+ */
+ public Throwable getCause() {
+ return cause;
+ }
+
+ /**
+ * The cause of this exception can only be set when constructed.
+ *
+ * @param cause Cause of the exception.
+ * @return This object.
+ * @throws java.lang.IllegalStateException This method will always throw an
+ * <code>IllegalStateException</code> since the cause of this
+ * exception can only be set when constructed.
+ * @since 1.1
+ */
+ public Throwable initCause(Throwable cause) {
+ throw new IllegalStateException();
+ }
+
+} \ No newline at end of file
diff --git a/bundles/org.eclipse.equinox.preferences/src/org/osgi/service/prefs/Preferences.java b/bundles/org.eclipse.equinox.preferences/src/org/osgi/service/prefs/Preferences.java
new file mode 100644
index 000000000..09dd2fd56
--- /dev/null
+++ b/bundles/org.eclipse.equinox.preferences/src/org/osgi/service/prefs/Preferences.java
@@ -0,0 +1,694 @@
+/*
+ * $Header: /home/eclipse/org.eclipse.core.runtime/src/org/osgi/service/prefs/Preferences.java,v 1.4 2005/11/11 22:07:19 johna Exp $
+ *
+ * Copyright (c) OSGi Alliance (2001, 2005). 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.
+ */
+package org.osgi.service.prefs;
+
+/**
+ * A node in a hierarchical collection of preference data.
+ *
+ * <p>
+ * This interface allows applications to store and retrieve user and system
+ * preference data. This data is stored persistently in an
+ * implementation-dependent backing store. Typical implementations include flat
+ * files, OS-specific registries, directory servers and SQL databases.
+ *
+ * <p>
+ * For each bundle, there is a separate tree of nodes for each user, and one for
+ * system preferences. The precise description of "user" and "system" will vary
+ * from one bundle to another. Typical information stored in the user preference
+ * tree might include font choice, and color choice for a bundle which interacts
+ * with the user via a servlet. Typical information stored in the system
+ * preference tree might include installation data, or things like high score
+ * information for a game program.
+ *
+ * <p>
+ * Nodes in a preference tree are named in a similar fashion to directories in a
+ * hierarchical file system. Every node in a preference tree has a <i>node name
+ * </i> (which is not necessarily unique), a unique <i>absolute path name </i>,
+ * and a path name <i>relative </i> to each ancestor including itself.
+ *
+ * <p>
+ * The root node has a node name of the empty <code>String</code> object ("").
+ * Every other node has an arbitrary node name, specified at the time it is
+ * created. The only restrictions on this name are that it cannot be the empty
+ * string, and it cannot contain the slash character ('/').
+ *
+ * <p>
+ * The root node has an absolute path name of <code>"/"</code>. Children of the
+ * root node have absolute path names of <code>"/" + </code> <i>&lt;node name&gt;
+ * </i>. All other nodes have absolute path names of <i>&lt;parent's absolute
+ * path name&gt; </i> <code> + "/" + </code> <i>&lt;node name&gt; </i>. Note that
+ * all absolute path names begin with the slash character.
+ *
+ * <p>
+ * A node <i>n </i>'s path name relative to its ancestor <i>a </i> is simply the
+ * string that must be appended to <i>a </i>'s absolute path name in order to
+ * form <i>n </i>'s absolute path name, with the initial slash character (if
+ * present) removed. Note that:
+ * <ul>
+ * <li>No relative path names begin with the slash character.
+ * <li>Every node's path name relative to itself is the empty string.
+ * <li>Every node's path name relative to its parent is its node name (except
+ * for the root node, which does not have a parent).
+ * <li>Every node's path name relative to the root is its absolute path name
+ * with the initial slash character removed.
+ * </ul>
+ *
+ * <p>
+ * Note finally that:
+ * <ul>
+ * <li>No path name contains multiple consecutive slash characters.
+ * <li>No path name with the exception of the root's absolute path name end in
+ * the slash character.
+ * <li>Any string that conforms to these two rules is a valid path name.
+ * </ul>
+ *
+ * <p>
+ * Each <code>Preference</code> node has zero or more properties associated with
+ * it, where a property consists of a name and a value. The bundle writer is
+ * free to choose any appropriate names for properties. Their values can be of
+ * type <code>String</code>,<code>long</code>,<code>int</code>,<code>boolean</code>,
+ * <code>byte[]</code>,<code>float</code>, or <code>double</code> but they can
+ * always be accessed as if they were <code>String</code> objects.
+ *
+ * <p>
+ * All node name and property name comparisons are case-sensitive.
+ *
+ * <p>
+ * All of the methods that modify preference data are permitted to operate
+ * asynchronously; they may return immediately, and changes will eventually
+ * propagate to the persistent backing store, with an implementation-dependent
+ * delay. The <code>flush</code> method may be used to synchronously force updates
+ * to the backing store.
+ *
+ * <p>
+ * Implementations must automatically attempt to flush to the backing store any
+ * pending updates for a bundle's preferences when the bundle is stopped or
+ * otherwise ungets the Preferences Service.
+ *
+ * <p>
+ * The methods in this class may be invoked concurrently by multiple threads in
+ * a single Java Virtual Machine (JVM) without the need for external
+ * synchronization, and the results will be equivalent to some serial execution.
+ * If this class is used concurrently <i>by multiple JVMs </i> that store their
+ * preference data in the same backing store, the data store will not be
+ * corrupted, but no other guarantees are made concerning the consistency of the
+ * preference data.
+ *
+ *
+ * @version $Revision: 1.4 $
+ */
+public interface Preferences {
+ /**
+ * Associates the specified value with the specified key in this node.
+ *
+ * @param key key with which the specified value is to be associated.
+ * @param value value to be associated with the specified key.
+ * @throws NullPointerException if <code>key</code> or <code>value</code> is
+ * <code>null</code>.
+ * @throws IllegalStateException if this node (or an ancestor) has been
+ * removed with the {@link #removeNode()}method.
+ */
+ public void put(String key, String value);
+
+ /**
+ * Returns the value associated with the specified <code>key</code> in this
+ * node. Returns the specified default if there is no value associated with
+ * the <code>key</code>, or the backing store is inaccessible.
+ *
+ * @param key key whose associated value is to be returned.
+ * @param def the value to be returned in the event that this node has no
+ * value associated with <code>key</code> or the backing store is
+ * inaccessible.
+ * @return the value associated with <code>key</code>, or <code>def</code> if
+ * no value is associated with <code>key</code>.
+ * @throws IllegalStateException if this node (or an ancestor) has been
+ * removed with the {@link #removeNode()}method.
+ * @throws NullPointerException if <code>key</code> is <code>null</code>. (A
+ * <code>null</code> default <i>is </i> permitted.)
+ */
+ public String get(String key, String def);
+
+ /**
+ * Removes the value associated with the specified <code>key</code> in this
+ * node, if any.
+ *
+ * @param key key whose mapping is to be removed from this node.
+ * @see #get(String,String)
+ * @throws IllegalStateException if this node (or an ancestor) has been
+ * removed with the {@link #removeNode()}method.
+ */
+ public void remove(String key);
+
+ /**
+ * Removes all of the properties (key-value associations) in this node. This
+ * call has no effect on any descendants of this node.
+ *
+ * @throws BackingStoreException if this operation cannot be completed due
+ * to a failure in the backing store, or inability to communicate
+ * with it.
+ * @throws IllegalStateException if this node (or an ancestor) has been
+ * removed with the {@link #removeNode()}method.
+ * @see #remove(String)
+ */
+ public void clear() throws BackingStoreException;
+
+ /**
+ * Associates a <code>String</code> object representing the specified
+ * <code>int</code> value with the specified <code>key</code> in this node. The
+ * associated string is the one that would be returned if the <code>int</code>
+ * value were passed to <code>Integer.toString(int)</code>. This method is
+ * intended for use in conjunction with {@link #getInt}method.
+ *
+ * <p>
+ * Implementor's note: it is <i>not </i> necessary that the property value
+ * be represented by a <code>String</code> object in the backing store. If the
+ * backing store supports integer values, it is not unreasonable to use
+ * them. This implementation detail is not visible through the
+ * <code>Preferences</code> API, which allows the value to be read as an
+ * <code>int</code> (with <code>getInt</code> or a <code>String</code> (with
+ * <code>get</code>) type.
+ *
+ * @param key key with which the string form of value is to be associated.
+ * @param value <code>value</code> whose string form is to be associated with
+ * <code>key</code>.
+ * @throws NullPointerException if <code>key</code> is <code>null</code>.
+ * @throws IllegalStateException if this node (or an ancestor) has been
+ * removed with the {@link #removeNode()}method.
+ * @see #getInt(String,int)
+ */
+ public void putInt(String key, int value);
+
+ /**
+ * Returns the <code>int</code> value represented by the <code>String</code>
+ * object associated with the specified <code>key</code> in this node. The
+ * <code>String</code> object is converted to an <code>int</code> as by
+ * <code>Integer.parseInt(String)</code>. Returns the specified default if
+ * there is no value associated with the <code>key</code>, the backing store
+ * is inaccessible, or if <code>Integer.parseInt(String)</code> would throw a
+ * <code>NumberFormatException</code> if the associated <code>value</code> were
+ * passed. This method is intended for use in conjunction with the
+ * {@link #putInt}method.
+ *
+ * @param key key whose associated value is to be returned as an
+ * <code>int</code>.
+ * @param def the value to be returned in the event that this node has no
+ * value associated with <code>key</code> or the associated value
+ * cannot be interpreted as an <code>int</code> or the backing store is
+ * inaccessible.
+ * @return the <code>int</code> value represented by the <code>String</code>
+ * object associated with <code>key</code> in this node, or
+ * <code>def</code> if the associated value does not exist or cannot
+ * be interpreted as an <code>int</code> type.
+ * @throws NullPointerException if <code>key</code> is <code>null</code>.
+ * @throws IllegalStateException if this node (or an ancestor) has been
+ * removed with the {@link #removeNode()}method.
+ * @see #putInt(String,int)
+ * @see #get(String,String)
+ */
+ public int getInt(String key, int def);
+
+ /**
+ * Associates a <code>String</code> object representing the specified
+ * <code>long</code> value with the specified <code>key</code> in this node. The
+ * associated <code>String</code> object is the one that would be returned if
+ * the <code>long</code> value were passed to <code>Long.toString(long)</code>.
+ * This method is intended for use in conjunction with the {@link #getLong}
+ * method.
+ *
+ * <p>
+ * Implementor's note: it is <i>not </i> necessary that the <code>value</code>
+ * be represented by a <code>String</code> type in the backing store. If the
+ * backing store supports <code>long</code> values, it is not unreasonable to
+ * use them. This implementation detail is not visible through the <code>
+ * Preferences</code> API, which allows the value to be read as a
+ * <code>long</code> (with <code>getLong</code> or a <code>String</code> (with
+ * <code>get</code>) type.
+ *
+ * @param key <code>key</code> with which the string form of <code>value</code>
+ * is to be associated.
+ * @param value <code>value</code> whose string form is to be associated with
+ * <code>key</code>.
+ * @throws NullPointerException if <code>key</code> is <code>null</code>.
+ * @throws IllegalStateException if this node (or an ancestor) has been
+ * removed with the {@link #removeNode()}method.
+ * @see #getLong(String,long)
+ */
+ public void putLong(String key, long value);
+
+ /**
+ * Returns the <code>long</code> value represented by the <code>String</code>
+ * object associated with the specified <code>key</code> in this node. The
+ * <code>String</code> object is converted to a <code>long</code> as by
+ * <code>Long.parseLong(String)</code>. Returns the specified default if
+ * there is no value associated with the <code>key</code>, the backing store
+ * is inaccessible, or if <code>Long.parseLong(String)</code> would throw a
+ * <code>NumberFormatException</code> if the associated <code>value</code> were
+ * passed. This method is intended for use in conjunction with the
+ * {@link #putLong}method.
+ *
+ * @param key <code>key</code> whose associated value is to be returned as a
+ * <code>long</code> value.
+ * @param def the value to be returned in the event that this node has no
+ * value associated with <code>key</code> or the associated value
+ * cannot be interpreted as a <code>long</code> type or the backing
+ * store is inaccessible.
+ * @return the <code>long</code> value represented by the <code>String</code>
+ * object associated with <code>key</code> in this node, or
+ * <code>def</code> if the associated value does not exist or cannot
+ * be interpreted as a <code>long</code> type.
+ * @throws NullPointerException if <code>key</code> is <code>null</code>.
+ * @throws IllegalStateException if this node (or an ancestor) has been
+ * removed with the {@link #removeNode()}method.
+ * @see #putLong(String,long)
+ * @see #get(String,String)
+ */
+ public long getLong(String key, long def);
+
+ /**
+ * Associates a <code>String</code> object representing the specified
+ * <code>boolean</code> value with the specified key in this node. The
+ * associated string is "true" if the value is <code>true</code>, and "false"
+ * if it is <code>false</code>. This method is intended for use in
+ * conjunction with the {@link #getBoolean}method.
+ *
+ * <p>
+ * Implementor's note: it is <i>not </i> necessary that the value be
+ * represented by a string in the backing store. If the backing store
+ * supports <code>boolean</code> values, it is not unreasonable to use them.
+ * This implementation detail is not visible through the <code>Preferences
+ * </code> API, which allows the value to be read as a <code>boolean</code>
+ * (with <code>getBoolean</code>) or a <code>String</code> (with <code>get</code>)
+ * type.
+ *
+ * @param key <code>key</code> with which the string form of value is to be
+ * associated.
+ * @param value value whose string form is to be associated with
+ * <code>key</code>.
+ * @throws NullPointerException if <code>key</code> is <code>null</code>.
+ * @throws IllegalStateException if this node (or an ancestor) has been
+ * removed with the {@link #removeNode()}method.
+ * @see #getBoolean(String,boolean)
+ * @see #get(String,String)
+ */
+ public void putBoolean(String key, boolean value);
+
+ /**
+ * Returns the <code>boolean</code> value represented by the <code>String</code>
+ * object associated with the specified <code>key</code> in this node. Valid
+ * strings are "true", which represents <code>true</code>, and "false", which
+ * represents <code>false</code>. Case is ignored, so, for example, "TRUE"
+ * and "False" are also valid. This method is intended for use in
+ * conjunction with the {@link #putBoolean}method.
+ *
+ * <p>
+ * Returns the specified default if there is no value associated with the
+ * <code>key</code>, the backing store is inaccessible, or if the associated
+ * value is something other than "true" or "false", ignoring case.
+ *
+ * @param key <code>key</code> whose associated value is to be returned as a
+ * <code>boolean</code>.
+ * @param def the value to be returned in the event that this node has no
+ * value associated with <code>key</code> or the associated value
+ * cannot be interpreted as a <code>boolean</code> or the backing store
+ * is inaccessible.
+ * @return the <code>boolean</code> value represented by the <code>String</code>
+ * object associated with <code>key</code> in this node, or
+ * <code>null</code> if the associated value does not exist or cannot
+ * be interpreted as a <code>boolean</code>.
+ * @throws NullPointerException if <code>key</code> is <code>null</code>.
+ * @throws IllegalStateException if this node (or an ancestor) has been
+ * removed with the {@link #removeNode()}method.
+ * @see #get(String,String)
+ * @see #putBoolean(String,boolean)
+ */
+ public boolean getBoolean(String key, boolean def);
+
+ /**
+ * Associates a <code>String</code> object representing the specified
+ * <code>float</code> value with the specified <code>key</code> in this node.
+ * The associated <code>String</code> object is the one that would be returned
+ * if the <code>float</code> value were passed to
+ * <code>Float.toString(float)</code>. This method is intended for use in
+ * conjunction with the {@link #getFloat}method.
+ *
+ * <p>
+ * Implementor's note: it is <i>not </i> necessary that the value be
+ * represented by a string in the backing store. If the backing store
+ * supports <code>float</code> values, it is not unreasonable to use them.
+ * This implementation detail is not visible through the <code>Preferences
+ * </code> API, which allows the value to be read as a <code>float</code> (with
+ * <code>getFloat</code>) or a <code>String</code> (with <code>get</code>) type.
+ *
+ * @param key <code>key</code> with which the string form of value is to be
+ * associated.
+ * @param value value whose string form is to be associated with
+ * <code>key</code>.
+ * @throws NullPointerException if <code>key</code> is <code>null</code>.
+ * @throws IllegalStateException if this node (or an ancestor) has been
+ * removed with the {@link #removeNode()}method.
+ * @see #getFloat(String,float)
+ */
+ public void putFloat(String key, float value);
+
+ /**
+ * Returns the float <code>value</code> represented by the <code>String</code>
+ * object associated with the specified <code>key</code> in this node. The
+ * <code>String</code> object is converted to a <code>float</code> value as by
+ * <code>Float.parseFloat(String)</code>. Returns the specified default if
+ * there is no value associated with the <code>key</code>, the backing store
+ * is inaccessible, or if <code>Float.parseFloat(String)</code> would throw a
+ * <code>NumberFormatException</code> if the associated value were passed.
+ * This method is intended for use in conjunction with the {@link #putFloat}
+ * method.
+ *
+ * @param key <code>key</code> whose associated value is to be returned as a
+ * <code>float</code> value.
+ * @param def the value to be returned in the event that this node has no
+ * value associated with <code>key</code> or the associated value
+ * cannot be interpreted as a <code>float</code> type or the backing
+ * store is inaccessible.
+ * @return the <code>float</code> value represented by the string associated
+ * with <code>key</code> in this node, or <code>def</code> if the
+ * associated value does not exist or cannot be interpreted as a
+ * <code>float</code> type.
+ * @throws IllegalStateException if this node (or an ancestor) has been
+ * removed with the {@link #removeNode()}method.
+ * @throws NullPointerException if <code>key</code> is <code>null</code>.
+ * @see #putFloat(String,float)
+ * @see #get(String,String)
+ */
+ public float getFloat(String key, float def);
+
+ /**
+ * Associates a <code>String</code> object representing the specified
+ * <code>double</code> value with the specified <code>key</code> in this node.
+ * The associated <code>String</code> object is the one that would be returned
+ * if the <code>double</code> value were passed to
+ * <code>Double.toString(double)</code>. This method is intended for use in
+ * conjunction with the {@link #getDouble}method
+ *
+ * <p>
+ * Implementor's note: it is <i>not </i> necessary that the value be
+ * represented by a string in the backing store. If the backing store
+ * supports <code>double</code> values, it is not unreasonable to use them.
+ * This implementation detail is not visible through the <code>Preferences
+ * </code> API, which allows the value to be read as a <code>double</code> (with
+ * <code>getDouble</code>) or a <code>String</code> (with <code>get</code>)
+ * type.
+ *
+ * @param key <code>key</code> with which the string form of value is to be
+ * associated.
+ * @param value value whose string form is to be associated with
+ * <code>key</code>.
+ * @throws NullPointerException if <code>key</code> is <code>null</code>.
+ * @throws IllegalStateException if this node (or an ancestor) has been
+ * removed with the {@link #removeNode()}method.
+ * @see #getDouble(String,double)
+ */
+ public void putDouble(String key, double value);
+
+ /**
+ * Returns the <code>double</code> value represented by the <code>String</code>
+ * object associated with the specified <code>key</code> in this node. The
+ * <code>String</code> object is converted to a <code>double</code> value as by
+ * <code>Double.parseDouble(String)</code>. Returns the specified default if
+ * there is no value associated with the <code>key</code>, the backing store
+ * is inaccessible, or if <code>Double.parseDouble(String)</code> would throw
+ * a <code>NumberFormatException</code> if the associated value were passed.
+ * This method is intended for use in conjunction with the
+ * {@link #putDouble}method.
+ *
+ * @param key <code>key</code> whose associated value is to be returned as a
+ * <code>double</code> value.
+ * @param def the value to be returned in the event that this node has no
+ * value associated with <code>key</code> or the associated value
+ * cannot be interpreted as a <code>double</code> type or the backing
+ * store is inaccessible.
+ * @return the <code>double</code> value represented by the <code>String</code>
+ * object associated with <code>key</code> in this node, or
+ * <code>def</code> if the associated value does not exist or cannot
+ * be interpreted as a <code>double</code> type.
+ * @throws IllegalStateException if this node (or an ancestor) has been
+ * removed with the the {@link #removeNode()}method.
+ * @throws NullPointerException if <code>key</code> is <code>null</code>.
+ * @see #putDouble(String,double)
+ * @see #get(String,String)
+ */
+ public double getDouble(String key, double def);
+
+ /**
+ * Associates a <code>String</code> object representing the specified
+ * <code>byte[]</code> with the specified <code>key</code> in this node. The
+ * associated <code>String</code> object the <i>Base64 </i> encoding of the
+ * <code>byte[]</code>, as defined in <a
+ * href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045 </a>, Section 6.8,
+ * with one minor change: the string will consist solely of characters from
+ * the <i>Base64 Alphabet </i>; it will not contain any newline characters.
+ * This method is intended for use in conjunction with the
+ * {@link #getByteArray}method.
+ *
+ * <p>
+ * Implementor's note: it is <i>not </i> necessary that the value be
+ * represented by a <code>String</code> type in the backing store. If the
+ * backing store supports <code>byte[]</code> values, it is not unreasonable
+ * to use them. This implementation detail is not visible through the <code>
+ * Preferences</code> API, which allows the value to be read as an a
+ * <code>byte[]</code> object (with <code>getByteArray</code>) or a
+ * <code>String</code> object (with <code>get</code>).
+ *
+ * @param key <code>key</code> with which the string form of <code>value</code>
+ * is to be associated.
+ * @param value <code>value</code> whose string form is to be associated with
+ * <code>key</code>.
+ * @throws NullPointerException if <code>key</code> or <code>value</code> is
+ * <code>null</code>.
+ * @throws IllegalStateException if this node (or an ancestor) has been
+ * removed with the {@link #removeNode()}method.
+ * @see #getByteArray(String,byte[])
+ * @see #get(String,String)
+ */
+ public void putByteArray(String key, byte[] value);
+
+ /**
+ * Returns the <code>byte[]</code> value represented by the <code>String</code>
+ * object associated with the specified <code>key</code> in this node. Valid
+ * <code>String</code> objects are <i>Base64 </i> encoded binary data, as
+ * defined in <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045 </a>,
+ * Section 6.8, with one minor change: the string must consist solely of
+ * characters from the <i>Base64 Alphabet </i>; no newline characters or
+ * extraneous characters are permitted. This method is intended for use in
+ * conjunction with the {@link #putByteArray}method.
+ *
+ * <p>
+ * Returns the specified default if there is no value associated with the
+ * <code>key</code>, the backing store is inaccessible, or if the associated
+ * value is not a valid Base64 encoded byte array (as defined above).
+ *
+ * @param key <code>key</code> whose associated value is to be returned as a
+ * <code>byte[]</code> object.
+ * @param def the value to be returned in the event that this node has no
+ * value associated with <code>key</code> or the associated value
+ * cannot be interpreted as a <code>byte[]</code> type, or the backing
+ * store is inaccessible.
+ * @return the <code>byte[]</code> value represented by the <code>String</code>
+ * object associated with <code>key</code> in this node, or
+ * <code>def</code> if the associated value does not exist or cannot
+ * be interpreted as a <code>byte[]</code>.
+ * @throws NullPointerException if <code>key</code> is <code>null</code>. (A
+ * <code>null</code> value for <code>def</code> <i>is </i> permitted.)
+ * @throws IllegalStateException if this node (or an ancestor) has been
+ * removed with the {@link #removeNode()}method.
+ * @see #get(String,String)
+ * @see #putByteArray(String,byte[])
+ */
+ public byte[] getByteArray(String key, byte[] def);
+
+ /**
+ * Returns all of the keys that have an associated value in this node. (The
+ * returned array will be of size zero if this node has no preferences and
+ * not <code>null</code>!)
+ *
+ * @return an array of the keys that have an associated value in this node.
+ * @throws BackingStoreException if this operation cannot be completed due
+ * to a failure in the backing store, or inability to communicate
+ * with it.
+ * @throws IllegalStateException if this node (or an ancestor) has been
+ * removed with the {@link #removeNode()}method.
+ */
+ public String[] keys() throws BackingStoreException;
+
+ /**
+ * Returns the names of the children of this node. (The returned array will
+ * be of size zero if this node has no children and not <code>null</code>!)
+ *
+ * @return the names of the children of this node.
+ * @throws BackingStoreException if this operation cannot be completed due
+ * to a failure in the backing store, or inability to communicate
+ * with it.
+ * @throws IllegalStateException if this node (or an ancestor) has been
+ * removed with the {@link #removeNode()}method.
+ */
+ public String[] childrenNames() throws BackingStoreException;
+
+ /**
+ * Returns the parent of this node, or <code>null</code> if this is the root.
+ *
+ * @return the parent of this node.
+ * @throws IllegalStateException if this node (or an ancestor) has been
+ * removed with the {@link #removeNode()}method.
+ */
+ public Preferences parent();
+
+ /**
+ * Returns a named <code>Preferences</code> object (node), creating it and any
+ * of its ancestors if they do not already exist. Accepts a relative or
+ * absolute pathname. Absolute pathnames (which begin with <code>'/'</code>)
+ * are interpreted relative to the root of this node. Relative pathnames
+ * (which begin with any character other than <code>'/'</code>) are
+ * interpreted relative to this node itself. The empty string (<code>""</code>)
+ * is a valid relative pathname, referring to this node itself.
+ *
+ * <p>
+ * If the returned node did not exist prior to this call, this node and any
+ * ancestors that were created by this call are not guaranteed to become
+ * persistent until the <code>flush</code> method is called on the returned
+ * node (or one of its descendants).
+ *
+ * @param pathName the path name of the <code>Preferences</code> object to
+ * return.
+ * @return the specified <code>Preferences</code> object.
+ * @throws IllegalArgumentException if the path name is invalid.
+ * @throws IllegalStateException if this node (or an ancestor) has been
+ * removed with the {@link #removeNode()}method.
+ * @throws NullPointerException if path name is <code>null</code>.
+ * @see #flush()
+ */
+ public Preferences node(String pathName);
+
+ /**
+ * Returns true if the named node exists. Accepts a relative or absolute
+ * pathname. Absolute pathnames (which begin with <code>'/'</code>) are
+ * interpreted relative to the root of this node. Relative pathnames (which
+ * begin with any character other than <code>'/'</code>) are interpreted
+ * relative to this node itself. The pathname <code>""</code> is valid, and
+ * refers to this node itself.
+ *
+ * <p>
+ * If this node (or an ancestor) has already been removed with the
+ * {@link #removeNode()}method, it <i>is </i> legal to invoke this method,
+ * but only with the pathname <code>""</code>; the invocation will return
+ * <code>false</code>. Thus, the idiom <code>p.nodeExists("")</code> may be
+ * used to test whether <code>p</code> has been removed.
+ *
+ * @param pathName the path name of the node whose existence is to be
+ * checked.
+ * @return true if the specified node exists.
+ * @throws BackingStoreException if this operation cannot be completed due
+ * to a failure in the backing store, or inability to communicate
+ * with it.
+ * @throws IllegalStateException if this node (or an ancestor) has been
+ * removed with the {@link #removeNode()}method and
+ * <code>pathname</code> is not the empty string (<code>""</code>).
+ * @throws IllegalArgumentException if the path name is invalid (i.e., it
+ * contains multiple consecutive slash characters, or ends with a
+ * slash character and is more than one character long).
+ */
+ public boolean nodeExists(String pathName)
+ throws BackingStoreException;
+
+ /**
+ * Removes this node and all of its descendants, invalidating any properties
+ * contained in the removed nodes. Once a node has been removed, attempting
+ * any method other than <code>name()</code>,<code>absolutePath()</code> or
+ * <code>nodeExists("")</code> on the corresponding <code>Preferences</code>
+ * instance will fail with an <code>IllegalStateException</code>. (The
+ * methods defined on <code>Object</code> can still be invoked on a node after
+ * it has been removed; they will not throw <code>IllegalStateException</code>.)
+ *
+ * <p>
+ * The removal is not guaranteed to be persistent until the <code>flush</code>
+ * method is called on the parent of this node.
+ *
+ * @throws IllegalStateException if this node (or an ancestor) has already
+ * been removed with the {@link #removeNode()}method.
+ * @throws BackingStoreException if this operation cannot be completed due
+ * to a failure in the backing store, or inability to communicate
+ * with it.
+ * @see #flush()
+ */
+ public void removeNode() throws BackingStoreException;
+
+ /**
+ * Returns this node's name, relative to its parent.
+ *
+ * @return this node's name, relative to its parent.
+ */
+ public String name();
+
+ /**
+ * Returns this node's absolute path name. Note that:
+ * <ul>
+ * <li>Root node - The path name of the root node is <code>"/"</code>.
+ * <li>Slash at end - Path names other than that of the root node may not
+ * end in slash (<code>'/'</code>).
+ * <li>Unusual names -<code>"."</code> and <code>".."</code> have <i>no </i>
+ * special significance in path names.
+ * <li>Illegal names - The only illegal path names are those that contain
+ * multiple consecutive slashes, or that end in slash and are not the root.
+ * </ul>
+ *
+ * @return this node's absolute path name.
+ */
+ public String absolutePath();
+
+ /**
+ * Forces any changes in the contents of this node and its descendants to
+ * the persistent store.
+ *
+ * <p>
+ * Once this method returns successfully, it is safe to assume that all
+ * changes made in the subtree rooted at this node prior to the method
+ * invocation have become permanent.
+ *
+ * <p>
+ * Implementations are free to flush changes into the persistent store at
+ * any time. They do not need to wait for this method to be called.
+ *
+ * <p>
+ * When a flush occurs on a newly created node, it is made persistent, as
+ * are any ancestors (and descendants) that have yet to be made persistent.
+ * Note however that any properties value changes in ancestors are <i>not
+ * </i> guaranteed to be made persistent.
+ *
+ * @throws BackingStoreException if this operation cannot be completed due
+ * to a failure in the backing store, or inability to communicate
+ * with it.
+ * @throws IllegalStateException if this node (or an ancestor) has been
+ * removed with the {@link #removeNode()}method.
+ * @see #sync()
+ */
+ public void flush() throws BackingStoreException;
+
+ /**
+ * Ensures that future reads from this node and its descendants reflect any
+ * changes that were committed to the persistent store (from any VM) prior
+ * to the <code>sync</code> invocation. As a side-effect, forces any changes
+ * in the contents of this node and its descendants to the persistent store,
+ * as if the <code>flush</code> method had been invoked on this node.
+ *
+ * @throws BackingStoreException if this operation cannot be completed due
+ * to a failure in the backing store, or inability to communicate
+ * with it.
+ * @throws IllegalStateException if this node (or an ancestor) has been
+ * removed with the {@link #removeNode()}method.
+ * @see #flush()
+ */
+ public void sync() throws BackingStoreException;
+}
diff --git a/bundles/org.eclipse.equinox.preferences/src/org/osgi/service/prefs/PreferencesService.java b/bundles/org.eclipse.equinox.preferences/src/org/osgi/service/prefs/PreferencesService.java
new file mode 100644
index 000000000..41331bcc0
--- /dev/null
+++ b/bundles/org.eclipse.equinox.preferences/src/org/osgi/service/prefs/PreferencesService.java
@@ -0,0 +1,48 @@
+/*
+ * $Header: /home/eclipse/org.eclipse.core.runtime/src/org/osgi/service/prefs/PreferencesService.java,v 1.4 2005/11/11 22:07:19 johna Exp $
+ *
+ * Copyright (c) OSGi Alliance (2001, 2005). 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.
+ */
+package org.osgi.service.prefs;
+
+/**
+ * The Preferences Service.
+ *
+ * <p>
+ * Each bundle using this service has its own set of preference trees: one for
+ * system preferences, and one for each user.
+ *
+ * <p>
+ * A <code>PreferencesService</code> object is specific to the bundle which
+ * obtained it from the service registry. If a bundle wishes to allow another
+ * bundle to access its preferences, it should pass its
+ * <code>PreferencesService</code> object to that bundle.
+ *
+ */
+public interface PreferencesService {
+ /**
+ * Returns the root system node for the calling bundle.
+ *
+ * @return The root system node for the calling bundle.
+ */
+ public Preferences getSystemPreferences();
+
+ /**
+ * Returns the root node for the specified user and the calling bundle.
+ *
+ * @param name The user for which to return the preference root node.
+ * @return The root node for the specified user and the calling bundle.
+ */
+ public Preferences getUserPreferences(String name);
+
+ /**
+ * Returns the names of users for which node trees exist.
+ *
+ * @return The names of users for which node trees exist.
+ */
+ public String[] getUsers();
+}
diff --git a/bundles/org.eclipse.equinox.registry/.classpath b/bundles/org.eclipse.equinox.registry/.classpath
new file mode 100644
index 000000000..751c8f2e5
--- /dev/null
+++ b/bundles/org.eclipse.equinox.registry/.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.registry/.cvsignore b/bundles/org.eclipse.equinox.registry/.cvsignore
new file mode 100644
index 000000000..ba077a403
--- /dev/null
+++ b/bundles/org.eclipse.equinox.registry/.cvsignore
@@ -0,0 +1 @@
+bin
diff --git a/bundles/org.eclipse.equinox.registry/.options b/bundles/org.eclipse.equinox.registry/.options
new file mode 100644
index 000000000..d08d97494
--- /dev/null
+++ b/bundles/org.eclipse.equinox.registry/.options
@@ -0,0 +1,4 @@
+# Turn on debugging for the registry.
+org.eclipse.equinox.registry/debug=false
+# Extension registry change events
+org.eclipse.equinox.registry/debug/events=false
diff --git a/bundles/org.eclipse.equinox.registry/.project b/bundles/org.eclipse.equinox.registry/.project
new file mode 100644
index 000000000..0696648d1
--- /dev/null
+++ b/bundles/org.eclipse.equinox.registry/.project
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>org.eclipse.equinox.registry</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.registry/META-INF/MANIFEST.MF b/bundles/org.eclipse.equinox.registry/META-INF/MANIFEST.MF
new file mode 100644
index 000000000..569639eb9
--- /dev/null
+++ b/bundles/org.eclipse.equinox.registry/META-INF/MANIFEST.MF
@@ -0,0 +1,18 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: %pluginName
+Bundle-SymbolicName: org.eclipse.equinox.registry; singleton:=true
+Bundle-Version: 1.0.0.qualifier
+Bundle-Localization: plugin
+Export-Package: org.eclipse.core.internal.registry;x-friends:="org.eclipse.core.runtime",
+ org.eclipse.core.internal.registry.osgi;x-friends:="org.eclipse.core.runtime",
+ org.eclipse.core.internal.registry.spi;x-internal:=true,
+ org.eclipse.equinox.registry.spi,
+ org.eclipse.core.runtime,
+ org.eclipse.equinox.registry,
+ org.eclipse.equinox.registry.tracker
+Require-Bundle: org.eclipse.equinox.common,
+ system.bundle;resolution:=optional
+Bundle-Vendor: %providerName
+Bundle-Activator: org.eclipse.core.internal.registry.osgi.Activator
+Import-Package: org.eclipse.core.runtime.jobs;resolution:=optional
diff --git a/bundles/org.eclipse.equinox.registry/about.html b/bundles/org.eclipse.equinox.registry/about.html
new file mode 100644
index 000000000..405526e24
--- /dev/null
+++ b/bundles/org.eclipse.equinox.registry/about.html
@@ -0,0 +1,51 @@
+<!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 (&quot;Content&quot;). Unless otherwise indicated below, the Content is provided to you under the terms and conditions of the
+Eclipse Public License Version 1.0 (&quot;EPL&quot;). 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, &quot;Program&quot; 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 (&quot;Redistributor&quot;) 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>Jakarta Commons Collections</b></p>
+<p>The plug-in includes software developed by The Apache Software Foundation as part of the Jakarta Commons Collections project.</p>
+
+<p>The Jakarta Commons Collections binary code can be found in the plug-in JAR in the following files:</p>
+
+<ul>
+ <li>org\eclipse\core\internal\registry\ReferenceMap.class</li>
+</ul>
+
+<p>The Jakarta Commons Collections source code can be found in src.zip in the following files:</p>
+
+<ul>
+ <li>org\eclipse\core\internal\registry\ReferenceMap.java</li>
+</ul>
+
+<p>Your use of the Jakara Commons Collections code is subject to the terms and conditions of the Apache License, Version 2.0. A copy of the license is contained
+in the file <a href="about_files/asl-v20.txt" target="_blank">about_files/asl-v20.txt</a> and is also available at <a href="http://www.apache.org/licenses/LICENSE-2.0.html" target="_blank">http://www.apache.org/licenses/LICENSE-2.0.html</a>.
+
+<p>The Apache attribution notice file <a href="about_files/NOTICE.TXT" target="_blank">about_files/NOTICE.TXT</a> is included with the Content in accordance with 4d of the Apache License, Version 2.0
+
+<p>The names &quot;Jakarta&quot; and &quot;Apache Software Foundation&quot; must not be used to endorse or promote products derived from this
+software without prior written permission. For written permission, please contact <a href="mailto:apache@apache.org">apache@apache.org</a>.</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.registry/about_files/NOTICE.txt b/bundles/org.eclipse.equinox.registry/about_files/NOTICE.txt
new file mode 100644
index 000000000..3f59805ce
--- /dev/null
+++ b/bundles/org.eclipse.equinox.registry/about_files/NOTICE.txt
@@ -0,0 +1,2 @@
+This product includes software developed by
+The Apache Software Foundation (http://www.apache.org/).
diff --git a/bundles/org.eclipse.equinox.registry/about_files/asl-v20.txt b/bundles/org.eclipse.equinox.registry/about_files/asl-v20.txt
new file mode 100644
index 000000000..d64569567
--- /dev/null
+++ b/bundles/org.eclipse.equinox.registry/about_files/asl-v20.txt
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/bundles/org.eclipse.equinox.registry/build.properties b/bundles/org.eclipse.equinox.registry/build.properties
new file mode 100644
index 000000000..3503fa716
--- /dev/null
+++ b/bundles/org.eclipse.equinox.registry/build.properties
@@ -0,0 +1,20 @@
+###############################################################################
+# 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/,\
+ .,\
+ .options,\
+ plugin.properties,\
+ about.html,\
+ about_files/
+src.includes = about.html,\
+ about_files/
diff --git a/bundles/org.eclipse.equinox.registry/plugin.properties b/bundles/org.eclipse.equinox.registry/plugin.properties
new file mode 100644
index 000000000..780c86fc8
--- /dev/null
+++ b/bundles/org.eclipse.equinox.registry/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 = Extension Registry Support
+providerName = Eclipse.org
diff --git a/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/ConfigurationElement.java b/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/ConfigurationElement.java
new file mode 100644
index 000000000..3319961d9
--- /dev/null
+++ b/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/ConfigurationElement.java
@@ -0,0 +1,280 @@
+/*******************************************************************************
+ * 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.registry;
+
+import java.util.Hashtable;
+import org.eclipse.core.runtime.*;
+import org.eclipse.equinox.registry.IExecutableExtension;
+import org.eclipse.equinox.registry.IExecutableExtensionFactory;
+import org.eclipse.osgi.util.NLS;
+
+/**
+ * An object which represents the user-defined contents of an extension
+ * in a plug-in manifest.
+ */
+public class ConfigurationElement extends RegistryObject {
+ static final ConfigurationElement[] EMPTY_ARRAY = new ConfigurationElement[0];
+
+ //The id of the parent element. It can be a configuration element or an extension
+ int parentId;
+ byte parentType; //This value is only interesting when running from cache.
+
+ //Store the properties and the value of the configuration element.
+ //The format is the following:
+ // [p1, v1, p2, v2, configurationElementValue]
+ //If the array size is even, there is no "configurationElementValue (ie getValue returns null)".
+ //The properties and their values are alternated (v1 is the value of p1).
+ private String[] propertiesAndValue;
+
+ //The name of the configuration element
+ private String name;
+
+ //The ID of the namespace owner.
+ //This value can be null when the element is loaded from disk and the owner has been uninstalled.
+ //This happens when the configuration is obtained from a delta containing removed extension.
+ protected long namespaceOwnerId;
+
+ protected ConfigurationElement(ExtensionRegistry registry) {
+ super(registry);
+ }
+
+ protected ConfigurationElement(int self, long contributorId, long namespaceOwnerId, String name, String[] propertiesAndValue, int[] children, int extraDataOffset, int parent, byte parentType, ExtensionRegistry registry) {
+ super(registry);
+
+ setObjectId(self);
+ this.namespaceOwnerId = namespaceOwnerId;
+ this.name = name;
+ this.propertiesAndValue = propertiesAndValue;
+ setRawChildren(children);
+ this.extraDataOffset = extraDataOffset;
+ parentId = parent;
+ this.parentType = parentType;
+
+ // resolve namespace owner
+ if (namespaceOwnerId == -1)
+ this.namespaceOwnerId = this.registry.getNamespaceOwnerId(contributorId);
+ }
+
+ void throwException(String message, Throwable exception) throws CoreException {
+ throw new CoreException(new Status(IStatus.ERROR, RegistryMessages.OWNER_NAME, IRegistryConstants.PLUGIN_ERROR, message, exception));
+ }
+
+ protected String getValue() {
+ return getValueAsIs();
+ }
+
+ String getValueAsIs() {
+ if (propertiesAndValue.length != 0 && propertiesAndValue.length % 2 == 1)
+ return propertiesAndValue[propertiesAndValue.length - 1];
+ return null;
+ }
+
+ public String getAttribute(String attrName) {
+ return getAttributeAsIs(attrName);
+ }
+
+ String getAttributeAsIs(String attrName) {
+ if (propertiesAndValue.length <= 1)
+ return null;
+ int size = propertiesAndValue.length - (propertiesAndValue.length % 2);
+ for (int i = 0; i < size; i += 2) {
+ if (propertiesAndValue[i].equals(attrName))
+ return propertiesAndValue[i + 1];
+ }
+ return null;
+ }
+
+ protected String[] getAttributeNames() {
+ if (propertiesAndValue.length <= 1)
+ return RegistryObjectManager.EMPTY_STRING_ARRAY;
+
+ int size = propertiesAndValue.length / 2;
+ String[] result = new String[size];
+ for (int i = 0; i < size; i++) {
+ result[i] = propertiesAndValue[i * 2];
+ }
+ return result;
+ }
+
+ void setProperties(String[] value) {
+ propertiesAndValue = value;
+ }
+
+ protected String[] getPropertiesAndValue() {
+ return propertiesAndValue;
+ }
+
+ void setValue(String value) {
+ if (propertiesAndValue.length == 0) {
+ propertiesAndValue = new String[] {value};
+ return;
+ }
+ if (propertiesAndValue.length % 2 == 1) {
+ propertiesAndValue[propertiesAndValue.length - 1] = value;
+ return;
+ }
+ String[] newPropertiesAndValue = new String[propertiesAndValue.length + 1];
+ System.arraycopy(propertiesAndValue, 0, newPropertiesAndValue, 0, propertiesAndValue.length);
+ newPropertiesAndValue[propertiesAndValue.length] = value;
+ propertiesAndValue = newPropertiesAndValue;
+ }
+
+ void setNamespaceOwnerId(long namespaceOwnerId) {
+ this.namespaceOwnerId = namespaceOwnerId;
+ }
+
+ protected long getNamespaceOwnerId() {
+ return namespaceOwnerId;
+ }
+
+ public ConfigurationElement[] getChildren(String childrenName) {
+ if (getRawChildren().length == 0)
+ return ConfigurationElement.EMPTY_ARRAY;
+
+ ConfigurationElement[] result = new ConfigurationElement[1]; //Most of the time there is only one match
+ int idx = 0;
+ RegistryObjectManager objectManager = registry.getObjectManager();
+ for (int i = 0; i < children.length; i++) {
+ ConfigurationElement toTest = (ConfigurationElement) objectManager.getObject(children[i], extraDataOffset == -1 ? RegistryObjectManager.CONFIGURATION_ELEMENT : RegistryObjectManager.THIRDLEVEL_CONFIGURATION_ELEMENT);
+ if (toTest.name.equals(childrenName)) {
+ if (idx != 0) {
+ ConfigurationElement[] copy = new ConfigurationElement[result.length + 1];
+ System.arraycopy(result, 0, copy, 0, result.length);
+ result = copy;
+ }
+ result[idx++] = toTest;
+ }
+ }
+ if (idx == 0)
+ result = ConfigurationElement.EMPTY_ARRAY;
+ return result;
+ }
+
+ void setParentId(int objectId) {
+ parentId = objectId;
+ }
+
+ protected String getName() {
+ return name;
+ }
+
+ void setName(String name) {
+ this.name = name;
+ }
+
+ void setParentType(byte type) {
+ parentType = type;
+ }
+
+ protected String getNamespace() {
+ return registry.getNamespace(namespaceOwnerId);
+ }
+
+ protected Object createExecutableExtension(String attributeName) throws CoreException {
+ String prop = null;
+ String executable;
+ String contributorName = null;
+ String className = null;
+ Object initData = null;
+ int i;
+
+ if (attributeName != null)
+ prop = getAttribute(attributeName);
+ else {
+ // property not specified, try as element value
+ prop = getValue();
+ if (prop != null) {
+ prop = prop.trim();
+ if (prop.equals("")) //$NON-NLS-1$
+ prop = null;
+ }
+ }
+
+ if (prop == null) {
+ // property not defined, try as a child element
+ ConfigurationElement[] exec;
+ ConfigurationElement[] parms;
+ ConfigurationElement element;
+ Hashtable initParms;
+ String pname;
+
+ exec = getChildren(attributeName);
+ if (exec.length != 0) {
+ element = exec[0]; // assumes single definition
+ contributorName = element.getAttribute("plugin"); //$NON-NLS-1$
+ className = element.getAttribute("class"); //$NON-NLS-1$
+ parms = element.getChildren("parameter"); //$NON-NLS-1$
+ if (parms.length != 0) {
+ initParms = new Hashtable(parms.length + 1);
+ for (i = 0; i < parms.length; i++) {
+ pname = parms[i].getAttribute("name"); //$NON-NLS-1$
+ if (pname != null)
+ initParms.put(pname, parms[i].getAttribute("value")); //$NON-NLS-1$
+ }
+ if (!initParms.isEmpty())
+ initData = initParms;
+ }
+ } else {
+ // specified name is not a simple attribute nor child element
+ throwException(NLS.bind(RegistryMessages.exExt_extDefNotFound, attributeName), null);
+ }
+ } else {
+ // simple property or element value, parse it into its components
+ i = prop.indexOf(':');
+ if (i != -1) {
+ executable = prop.substring(0, i).trim();
+ initData = prop.substring(i + 1).trim();
+ } else
+ executable = prop;
+
+ i = executable.indexOf('/');
+ if (i != -1) {
+ contributorName = executable.substring(0, i).trim();
+ className = executable.substring(i + 1).trim();
+ } else
+ className = executable;
+ }
+
+ // create a new instance
+ Object result = null;
+
+ // check if alternative processing strategy is present
+ result = registry.processExecutableExtension(contributorName, namespaceOwnerId, getNamespace(), className, initData, attributeName, this);
+
+ // Check if we have extension adapter and initialize;
+ // Make the call even if the initialization string is null
+ try {
+ // We need to take into account both "old" and "new" style executable extensions
+ ConfigurationElementHandle confElementHandle = new ConfigurationElementHandle(registry.getObjectManager(), getObjectId());
+ if (result instanceof IExecutableExtension) {
+ ((IExecutableExtension) result).setInitializationData(confElementHandle, attributeName, initData);
+ }
+ if (registry.compatibilityStrategy != null)
+ registry.compatibilityStrategy.setInitializationData(result, confElementHandle, attributeName, initData);
+ } catch (CoreException ce) {
+ // user code threw exception
+ throw ce;
+ } catch (Exception te) {
+ // user code caused exception
+ throwException(NLS.bind(RegistryMessages.plugin_initObjectError, getNamespace(), className), te);
+ }
+
+ // Deal with executable extension factories.
+ if (result instanceof IExecutableExtensionFactory) {
+ result = ((IExecutableExtensionFactory) result).create();
+ }
+ if (registry.compatibilityStrategy != null)
+ result = registry.compatibilityStrategy.create(result);
+
+ return result;
+ }
+
+}
diff --git a/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/ConfigurationElementHandle.java b/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/ConfigurationElementHandle.java
new file mode 100644
index 000000000..02f38aa9f
--- /dev/null
+++ b/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/ConfigurationElementHandle.java
@@ -0,0 +1,128 @@
+/*******************************************************************************
+ * 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.registry;
+
+import org.eclipse.core.runtime.*;
+import org.eclipse.equinox.registry.IConfigurationElement;
+import org.eclipse.equinox.registry.IExtension;
+
+/**
+ * @since 3.1
+ */
+public class ConfigurationElementHandle extends Handle implements IConfigurationElement {
+ static final ConfigurationElementHandle[] EMPTY_ARRAY = new ConfigurationElementHandle[0];
+
+ public ConfigurationElementHandle(IObjectManager objectManager, int id) {
+ super(objectManager, id);
+ }
+
+ protected ConfigurationElement getConfigurationElement() {
+ return (ConfigurationElement) objectManager.getObject(getId(), RegistryObjectManager.CONFIGURATION_ELEMENT);
+ }
+
+ public String getAttribute(String propertyName) {
+ return getConfigurationElement().getAttribute(propertyName);
+ }
+
+ public String[] getAttributeNames() {
+ return getConfigurationElement().getAttributeNames();
+ }
+
+ public IConfigurationElement[] getChildren() {
+ ConfigurationElement actualCe = getConfigurationElement();
+ if (actualCe.extraDataOffset == -1) {
+ return (IConfigurationElement[]) objectManager.getHandles(actualCe.getRawChildren(), RegistryObjectManager.CONFIGURATION_ELEMENT);
+ }
+ return (IConfigurationElement[]) objectManager.getHandles(actualCe.getRawChildren(), RegistryObjectManager.THIRDLEVEL_CONFIGURATION_ELEMENT);
+ }
+
+ public Object createExecutableExtension(String propertyName) throws CoreException {
+ try {
+ return getConfigurationElement().createExecutableExtension(propertyName);
+ } catch (InvalidRegistryObjectException e) {
+ Status status = new Status(IStatus.ERROR, RegistryMessages.OWNER_NAME, IRegistryConstants.PLUGIN_ERROR, "Invalid registry object", e); //$NON-NLS-1$
+ if (objectManager instanceof RegistryObjectManager)
+ ((RegistryObjectManager) objectManager).getRegistry().log(status);
+ throw new CoreException(status);
+ }
+ }
+
+ public String getAttributeAsIs(String name) {
+ return getConfigurationElement().getAttributeAsIs(name);
+ }
+
+ public IConfigurationElement[] getChildren(String name) {
+ ConfigurationElement actualCE = getConfigurationElement();
+ ConfigurationElement[] children = (ConfigurationElement[]) objectManager.getObjects(actualCE.getRawChildren(), actualCE.extraDataOffset == -1 ? RegistryObjectManager.CONFIGURATION_ELEMENT : RegistryObjectManager.THIRDLEVEL_CONFIGURATION_ELEMENT);
+ if (children.length == 0)
+ return ConfigurationElementHandle.EMPTY_ARRAY;
+
+ IConfigurationElement[] result = new IConfigurationElement[1];
+ int idx = 0;
+ for (int i = 0; i < children.length; i++) {
+ if (children[i].getName().equals(name)) {
+ if (idx != 0) {
+ IConfigurationElement[] copy = new IConfigurationElement[result.length + 1];
+ System.arraycopy(result, 0, copy, 0, result.length);
+ result = copy;
+ }
+ result[idx++] = (IConfigurationElement) objectManager.getHandle(children[i].getObjectId(), actualCE.extraDataOffset == -1 ? RegistryObjectManager.CONFIGURATION_ELEMENT : RegistryObjectManager.THIRDLEVEL_CONFIGURATION_ELEMENT);
+ }
+ }
+ if (idx == 0)
+ return ConfigurationElementHandle.EMPTY_ARRAY;
+ return result;
+ }
+
+ public IExtension getDeclaringExtension() {
+ Object result = this;
+ while (!((result = ((ConfigurationElementHandle) result).getParent()) instanceof ExtensionHandle)) { /*do nothing*/
+ }
+ return (IExtension) result;
+ }
+
+ public String getName() {
+ return getConfigurationElement().getName();
+ }
+
+ public Object getParent() {
+ ConfigurationElement actualCe = getConfigurationElement();
+ return objectManager.getHandle(actualCe.parentId, actualCe.parentType);
+ }
+
+ public String getValue() {
+ return getConfigurationElement().getValue();
+ }
+
+ public String getValueAsIs() {
+ return getConfigurationElement().getValueAsIs();
+ }
+
+ RegistryObject getObject() {
+ return getConfigurationElement();
+ }
+
+ public String getNamespace() {
+ String result = getConfigurationElement().getNamespace();
+ if (result == null)
+ return getDeclaringExtension().getNamespace();
+ return result;
+ }
+
+ public boolean isValid() {
+ try {
+ getConfigurationElement();
+ } catch (InvalidRegistryObjectException e) {
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/Contribution.java b/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/Contribution.java
new file mode 100644
index 000000000..3ab6b2796
--- /dev/null
+++ b/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/Contribution.java
@@ -0,0 +1,97 @@
+/*******************************************************************************
+ * 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.registry;
+
+// This object is used to keep track on a contributor basis of the extension and extension points being contributed.
+// It is mainly used on removal so we can quickly find objects to remove.
+// Each contribution is made in the context of a namespace.
+public class Contribution implements KeyedElement {
+ static final int[] EMPTY_CHILDREN = new int[] {0, 0};
+
+ //The registry that owns this object
+ protected ExtensionRegistry registry;
+
+ // The actual contributor of the contribution.
+ protected long contributorId;
+
+ // The namespace containing this contribution.
+ protected String namespace = null;
+
+ // Id of the namespace owner (might be same or different from the contributorId).
+ protected long namespaceOwnerId = -1;
+
+ // This array stores the identifiers of both the extension points and the extensions.
+ // The array has always a minimum size of 2.
+ // The first element of the array is the number of extension points and the second the number of extensions.
+ // [numberOfExtensionPoints, numberOfExtensions, extensionPoint#1, extensionPoint#2, extensionPoint..., ext#1, ext#2, ext#3, ... ].
+ // The size of the array is 2 + (numberOfExtensionPoints + numberOfExtensions).
+ private int[] children = EMPTY_CHILDREN;
+ static final public byte EXTENSION_POINT = 0;
+ static final public byte EXTENSION = 1;
+
+ protected Contribution(long contributorId, ExtensionRegistry registry) {
+ this.contributorId = contributorId;
+ this.registry = registry;
+
+ // resolve namespace owner and namespace
+ namespaceOwnerId = registry.getNamespaceOwnerId(contributorId);
+ namespace = registry.getNamespace(contributorId);
+ }
+
+ void setRawChildren(int[] children) {
+ this.children = children;
+ }
+
+ protected long getContributorId() {
+ return contributorId;
+ }
+
+ protected int[] getRawChildren() {
+ return children;
+ }
+
+ protected int[] getExtensions() {
+ int[] results = new int[children[EXTENSION]];
+ System.arraycopy(children, 2 + children[EXTENSION_POINT], results, 0, children[EXTENSION]);
+ return results;
+ }
+
+ protected int[] getExtensionPoints() {
+ int[] results = new int[children[EXTENSION_POINT]];
+ System.arraycopy(children, 2, results, 0, children[EXTENSION_POINT]);
+ return results;
+ }
+
+ public String getNamespace() {
+ return namespace;
+ }
+
+ public String toString() {
+ return "Contribution: " + contributorId + " in namespace" + getNamespace(); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+
+ protected long getNamespaceOwnerId() {
+ return namespaceOwnerId;
+ }
+
+ //Implements the KeyedElement interface
+ public int getKeyHashCode() {
+ return getKey().hashCode();
+ }
+
+ public Object getKey() {
+ return new Long(contributorId);
+ }
+
+ public boolean compare(KeyedElement other) {
+ return contributorId == ((Contribution) other).contributorId;
+ }
+}
diff --git a/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/Extension.java b/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/Extension.java
new file mode 100644
index 000000000..e140fffd4
--- /dev/null
+++ b/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/Extension.java
@@ -0,0 +1,120 @@
+/*******************************************************************************
+ * 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.internal.registry;
+
+import java.lang.ref.SoftReference;
+
+/**
+ * An object which represents the user-defined extension in a plug-in manifest.
+ */
+public class Extension extends RegistryObject {
+ public static final Extension[] EMPTY_ARRAY = new Extension[0];
+
+ //Extension simple identifier
+ private String simpleId;
+ //The namespace for the extension.
+ private String namespace;
+
+ // Place holder for the label and the extension point. It contains either a String[] or a SoftReference to a String[].
+ //The array layout is [label, extension point name]
+ private Object extraInformation;
+ private static final byte LABEL = 0; //The human readable name of the extension
+ private static final byte XPT_NAME = 1; // The fully qualified name of the extension point to which this extension is attached to
+ private static final int EXTRA_SIZE = 2;
+
+ protected Extension(ExtensionRegistry registry) {
+ super(registry);
+ }
+
+ protected Extension(int self, String simpleId, String namespace, int[] children, int extraData, ExtensionRegistry registry) {
+ super(registry);
+
+ setObjectId(self);
+ this.simpleId = simpleId;
+ setRawChildren(children);
+ this.extraDataOffset = extraData;
+ this.namespace = namespace;
+ }
+
+ protected String getExtensionPointIdentifier() {
+ return getExtraData()[XPT_NAME];
+ }
+
+ protected String getSimpleIdentifier() {
+ return simpleId;
+ }
+
+ protected String getUniqueIdentifier() {
+ return simpleId == null ? null : this.getNamespace() + '.' + simpleId;
+ }
+
+ void setExtensionPointIdentifier(String value) {
+ ensureExtraInformationType();
+ ((String[]) extraInformation)[XPT_NAME] = value;
+ }
+
+ void setSimpleIdentifier(String value) {
+ simpleId = value;
+ }
+
+ private String[] getExtraData() {
+ //The extension has been created by parsing, or does not have any extra data
+ if (extraDataOffset == -1) {
+ if (extraInformation != null)
+ return (String[]) extraInformation;
+ return null;
+ }
+
+ //The extension has been loaded from the cache.
+ String[] result = null;
+ if (extraInformation == null || (result = ((extraInformation instanceof SoftReference) ? (String[]) ((SoftReference) extraInformation).get() : (String[]) extraInformation)) == null) {
+ result = registry.getCleanTableReader().loadExtensionExtraData(extraDataOffset);
+ extraInformation = new SoftReference(result);
+ }
+ return result;
+ }
+
+ String getLabel() {
+ String s = getExtraData()[LABEL];
+ if (s == null)
+ return ""; //$NON-NLS-1$
+ return s;
+ }
+
+ void setLabel(String value) {
+ ensureExtraInformationType();
+ ((String[]) extraInformation)[LABEL] = value;
+ }
+
+ public String getNamespace() {
+ return namespace;
+ }
+
+ void setNamespace(String value) {
+ namespace = value;
+ }
+
+ public String toString() {
+ return getUniqueIdentifier() + " -> " + getExtensionPointIdentifier(); //$NON-NLS-1$
+ }
+
+ /**
+ * At the end of this method, extra information will be a string[]
+ */
+ private void ensureExtraInformationType() {
+ if (extraInformation instanceof SoftReference) {
+ extraInformation = ((SoftReference) extraInformation).get();
+ }
+ if (extraInformation == null) {
+ extraInformation = new String[EXTRA_SIZE];
+ }
+ }
+}
diff --git a/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/ExtensionDelta.java b/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/ExtensionDelta.java
new file mode 100644
index 000000000..5d15126b3
--- /dev/null
+++ b/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/ExtensionDelta.java
@@ -0,0 +1,70 @@
+/*******************************************************************************
+ * 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.internal.registry;
+
+import org.eclipse.equinox.registry.*;
+
+public class ExtensionDelta implements IExtensionDelta {
+ private int kind;
+ private int extension;
+ private int extensionPoint;
+ private RegistryDelta containingDelta;
+
+ void setContainingDelta(RegistryDelta containingDelta) {
+ this.containingDelta = containingDelta;
+ }
+
+ int getExtensionId() {
+ return extension;
+ }
+
+ int getExtensionPointId() {
+ return extensionPoint;
+ }
+
+ public IExtensionPoint getExtensionPoint() {
+ return new ExtensionPointHandle(containingDelta.getObjectManager(), extensionPoint);
+ }
+
+ public void setExtensionPoint(int extensionPoint) {
+ this.extensionPoint = extensionPoint;
+ }
+
+ public int getKind() {
+ return kind;
+ }
+
+ public IExtension getExtension() {
+ return new ExtensionHandle(containingDelta.getObjectManager(), extension);
+ }
+
+ public void setExtension(int extension) {
+ this.extension = extension;
+ }
+
+ public void setKind(int kind) {
+ this.kind = kind;
+ }
+
+ public String toString() {
+ return "\n\t\t" + getExtensionPoint().getUniqueIdentifier() + " - " + getExtension().getNamespace() + '.' + getExtension().getSimpleIdentifier() + " (" + getKindString(this.getKind()) + ")"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
+ }
+
+ public static String getKindString(int kind) {
+ switch (kind) {
+ case ADDED :
+ return "ADDED"; //$NON-NLS-1$
+ case REMOVED :
+ return "REMOVED"; //$NON-NLS-1$
+ }
+ return "UNKNOWN"; //$NON-NLS-1$
+ }
+}
diff --git a/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/ExtensionHandle.java b/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/ExtensionHandle.java
new file mode 100644
index 000000000..39bb7973a
--- /dev/null
+++ b/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/ExtensionHandle.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.registry;
+
+import org.eclipse.core.runtime.InvalidRegistryObjectException;
+import org.eclipse.equinox.registry.IConfigurationElement;
+import org.eclipse.equinox.registry.IExtension;
+
+/**
+ * @since 3.1
+ */
+public class ExtensionHandle extends Handle implements IExtension {
+ static final ExtensionHandle[] EMPTY_ARRAY = new ExtensionHandle[0];
+
+ public ExtensionHandle(IObjectManager objectManager, int id) {
+ super(objectManager, id);
+ }
+
+ protected Extension getExtension() {
+ return (Extension) objectManager.getObject(getId(), RegistryObjectManager.EXTENSION);
+ }
+
+ public String getNamespace() {
+ return getExtension().getNamespace();
+ }
+
+ public String getExtensionPointUniqueIdentifier() {
+ return getExtension().getExtensionPointIdentifier();
+ }
+
+ public String getLabel() {
+ return getExtension().getLabel();
+ }
+
+ public String getSimpleIdentifier() {
+ return getExtension().getSimpleIdentifier();
+ }
+
+ public String getUniqueIdentifier() {
+ return getExtension().getUniqueIdentifier();
+ }
+
+ public IConfigurationElement[] getConfigurationElements() {
+ return (IConfigurationElement[]) objectManager.getHandles(getExtension().getRawChildren(), RegistryObjectManager.CONFIGURATION_ELEMENT);
+ }
+
+ RegistryObject getObject() {
+ return getExtension();
+ }
+
+ public boolean isValid() {
+ try {
+ getExtension();
+ } catch (InvalidRegistryObjectException e) {
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/ExtensionPoint.java b/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/ExtensionPoint.java
new file mode 100644
index 000000000..ad9cba6df
--- /dev/null
+++ b/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/ExtensionPoint.java
@@ -0,0 +1,130 @@
+/*******************************************************************************
+ * 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.internal.registry;
+
+import java.io.File;
+import java.lang.ref.SoftReference;
+
+/**
+ * An object which represents the user-defined extension point in a
+ * plug-in manifest.
+ */
+public class ExtensionPoint extends RegistryObject {
+ public static final ExtensionPoint[] EMPTY_ARRAY = new ExtensionPoint[0];
+
+ //Place holder for the label and the schema. It contains either a String[] or a SoftReference to a String[].
+ //The array layout is [label, schemaReference, fullyQualifiedName, namespace, contributorId]
+ private Object extraInformation;
+ //Indexes of the various fields
+ private static final byte LABEL = 0; //The human readable name for the extension point
+ private static final byte SCHEMA = 1; //The schema of the extension point
+ private static final byte QUALIFIED_NAME = 2; //The fully qualified name of the extension point
+ private static final byte NAMESPACE = 3; //The name of the namespace contributing the extension point
+ private static final byte CONTRIBUTOR_ID = 4; //The namespace owner contributing the extension point
+ private static final int EXTRA_SIZE = 5;
+
+ protected ExtensionPoint(ExtensionRegistry registry) {
+ super(registry);
+ }
+
+ protected ExtensionPoint(int self, int[] children, int dataOffset, ExtensionRegistry registry) {
+ super(registry);
+
+ setObjectId(self);
+ setRawChildren(children);
+ extraDataOffset = dataOffset;
+ }
+
+ protected String getSimpleIdentifier() {
+ return getUniqueIdentifier().substring(getUniqueIdentifier().lastIndexOf('.') + 1);
+ }
+
+ private String[] getExtraData() {
+ //The extension point has been created by parsing, or does not have any extra data
+ if (extraDataOffset == -1) { //When this is true, the extraInformation is always a String[]. This happens when the object is created by the parser.
+ if (extraInformation != null)
+ return (String[]) extraInformation;
+ return new String[EXTRA_SIZE];
+ }
+
+ //The extension point has been loaded from the cache.
+ String[] result = null;
+ if (extraInformation == null || (result = ((extraInformation instanceof SoftReference) ? (String[]) ((SoftReference) extraInformation).get() : (String[]) extraInformation)) == null) {
+ result = registry.getCleanTableReader().loadExtensionPointExtraData(extraDataOffset);
+ extraInformation = new SoftReference(result);
+ }
+ return result;
+ }
+
+ /**
+ * At the end of this method, extra information will be a string[]
+ */
+ private void ensureExtraInformationType() {
+ if (extraInformation instanceof SoftReference) {
+ extraInformation = ((SoftReference) extraInformation).get();
+ }
+ if (extraInformation == null) {
+ extraInformation = new String[EXTRA_SIZE];
+ }
+ }
+
+ protected String getSchemaReference() {
+ String[] result = getExtraData();
+ return result[1] == null ? "" : result[SCHEMA].replace(File.separatorChar, '/'); //$NON-NLS-1$
+ }
+
+ protected String getLabel() {
+ String[] result = getExtraData();
+ return result[0] == null ? "" : result[LABEL]; //$NON-NLS-1$
+ }
+
+ protected String getUniqueIdentifier() {
+ return getExtraData()[QUALIFIED_NAME];
+ }
+
+ public String getNamespace() {
+ return getExtraData()[NAMESPACE];
+ }
+
+ protected long getNamespaceOwnerId() {
+ return Long.parseLong(getExtraData()[CONTRIBUTOR_ID]);
+ }
+
+ void setSchema(String value) {
+ ensureExtraInformationType();
+ ((String[]) extraInformation)[SCHEMA] = value;
+ }
+
+ void setLabel(String value) {
+ ensureExtraInformationType();
+ ((String[]) extraInformation)[LABEL] = value;
+ }
+
+ void setUniqueIdentifier(String value) {
+ ensureExtraInformationType();
+ ((String[]) extraInformation)[QUALIFIED_NAME] = value;
+ }
+
+ void setNamespace(String value) {
+ ensureExtraInformationType();
+ ((String[]) extraInformation)[NAMESPACE] = value;
+ }
+
+ void setNamespaceOwnerId(long id) {
+ ensureExtraInformationType();
+ ((String[]) extraInformation)[CONTRIBUTOR_ID] = Long.toString(id);
+ }
+
+ public String toString() {
+ return getUniqueIdentifier();
+ }
+
+}
diff --git a/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/ExtensionPointHandle.java b/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/ExtensionPointHandle.java
new file mode 100644
index 000000000..a9f7e5f98
--- /dev/null
+++ b/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/ExtensionPointHandle.java
@@ -0,0 +1,94 @@
+/*******************************************************************************
+ * 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.registry;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import org.eclipse.core.runtime.InvalidRegistryObjectException;
+import org.eclipse.equinox.registry.*;
+
+/**
+ * @since 3.1
+ */
+public class ExtensionPointHandle extends Handle implements IExtensionPoint {
+ static final ExtensionPointHandle[] EMPTY_ARRAY = new ExtensionPointHandle[0];
+
+ public ExtensionPointHandle(IObjectManager objectManager, int id) {
+ super(objectManager, id);
+ }
+
+ public IExtension[] getExtensions() {
+ return (IExtension[]) objectManager.getHandles(getExtensionPoint().getRawChildren(), RegistryObjectManager.EXTENSION);
+ }
+
+ public String getNamespace() {
+ return getExtensionPoint().getNamespace();
+ }
+
+ public IExtension getExtension(String extensionId) {
+ if (extensionId == null)
+ return null;
+ int[] children = getExtensionPoint().getRawChildren();
+ for (int i = 0; i < children.length; i++) {
+ // Here we directly get the object because it avoids the creation of garbage and because we'll need the object anyway to compare the value
+ if (extensionId.equals(((Extension) objectManager.getObject(children[i], RegistryObjectManager.EXTENSION)).getUniqueIdentifier()))
+ return (ExtensionHandle) objectManager.getHandle(children[i], RegistryObjectManager.EXTENSION);
+ }
+ return null;
+ }
+
+ public IConfigurationElement[] getConfigurationElements() {
+ //get the actual extension objects since we'll need to get the configuration elements information.
+ Extension[] tmpExtensions = (Extension[]) objectManager.getObjects(getExtensionPoint().getRawChildren(), RegistryObjectManager.EXTENSION);
+ if (tmpExtensions.length == 0)
+ return ConfigurationElementHandle.EMPTY_ARRAY;
+
+ ArrayList result = new ArrayList();
+ for (int i = 0; i < tmpExtensions.length; i++) {
+ result.addAll(Arrays.asList(objectManager.getHandles(tmpExtensions[i].getRawChildren(), RegistryObjectManager.CONFIGURATION_ELEMENT)));
+ }
+ return (IConfigurationElement[]) result.toArray(new IConfigurationElement[result.size()]);
+ }
+
+ public String getLabel() {
+ return getExtensionPoint().getLabel();
+ }
+
+ public String getSchemaReference() {
+ return getExtensionPoint().getSchemaReference();
+ }
+
+ public String getSimpleIdentifier() {
+ return getExtensionPoint().getSimpleIdentifier();
+ }
+
+ public String getUniqueIdentifier() {
+ return getExtensionPoint().getUniqueIdentifier();
+ }
+
+ RegistryObject getObject() {
+ return getExtensionPoint();
+ }
+
+ protected ExtensionPoint getExtensionPoint() {
+ return (ExtensionPoint) objectManager.getObject(getId(), RegistryObjectManager.EXTENSION_POINT);
+ }
+
+ public boolean isValid() {
+ try {
+ getExtensionPoint();
+ } catch (InvalidRegistryObjectException e) {
+ return false;
+ }
+ return true;
+ }
+
+}
diff --git a/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/ExtensionRegistry.java b/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/ExtensionRegistry.java
new file mode 100644
index 000000000..c81005f63
--- /dev/null
+++ b/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/ExtensionRegistry.java
@@ -0,0 +1,1101 @@
+/*******************************************************************************
+ * 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.registry;
+
+import java.io.*;
+import java.lang.reflect.Array;
+import java.util.*;
+import javax.xml.parsers.ParserConfigurationException;
+import org.eclipse.core.internal.registry.spi.ExtensionDescription;
+import org.eclipse.core.internal.registry.spi.ExtensionProperty;
+import org.eclipse.core.runtime.*;
+import org.eclipse.core.runtime.adaptor.FileManager;
+import org.eclipse.equinox.registry.*;
+import org.eclipse.equinox.registry.spi.RegistryStrategy;
+import org.eclipse.osgi.util.NLS;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+/**
+ * An implementation for the extension registry API.
+ */
+public class ExtensionRegistry implements IExtensionRegistry {
+
+ protected class ListenerInfo {
+ public String filter;
+ public EventListener listener;
+
+ public ListenerInfo(EventListener listener, String filter) {
+ this.listener = listener;
+ this.filter = filter;
+ }
+
+ /**
+ * Used by ListenerList to ensure uniqueness.
+ */
+ public boolean equals(Object another) {
+ return another instanceof ListenerInfo && ((ListenerInfo) another).listener == this.listener;
+ }
+ }
+
+ // used to enforce concurrent access policy for readers/writers
+ private ReadWriteMonitor access = new ReadWriteMonitor();
+
+ // deltas not broadcasted yet. Deltas are kept organized by the namespace name (objects with the same namespace are grouped together)
+ private transient Map deltas = new HashMap(11);
+
+ //file manager associated with the registry cache
+ protected FileManager cacheFileManager;
+
+ // all registry change listeners
+ private transient ListenerList listeners = new ListenerList();
+
+ private RegistryObjectManager registryObjects = null;
+
+ // set to "true" if registry was able to use cache to populate it's content.
+ // if "false", content is empty and might need to be filled in
+ protected boolean isRegistryFilledFromCache = false;
+
+ // Table reader associated with this extension registry
+ protected TableReader theTableReader = new TableReader(this);
+
+ // The "key" to the object.
+ private Object token;
+
+ /////////////////////////////////////////////////////////////////////////////////////////
+ // Registry strategies
+ protected RegistryStrategy strategy;
+ protected ICompatibilityStrategy compatibilityStrategy = null;
+
+ public RegistryObjectManager getObjectManager() {
+ return registryObjects;
+ }
+
+ /**
+ * Sets new cache file manager. If existing file manager was owned by the registry,
+ * closes it.
+ *
+ * @param newFileManager - new cache file manager
+ * @param registryOwnsManager - true: life cycle of the file manager is controlled by the registry
+ */
+ protected void setFileManager(File cacheBase, boolean isCacheReadOnly) {
+ if (cacheFileManager != null)
+ cacheFileManager.close(); // close existing file manager first
+
+ if (cacheBase != null) {
+ cacheFileManager = new FileManager(cacheBase, isCacheReadOnly ? "none" : null, isCacheReadOnly); //$NON-NLS-1$
+ try {
+ cacheFileManager.open(!isCacheReadOnly);
+ } catch (IOException e) {
+ // Ignore the exception. The registry will be rebuilt from source.
+ }
+ }
+ }
+
+ /**
+ * Adds and resolves all extensions and extension points provided by the
+ * plug-in.
+ * <p>
+ * A corresponding IRegistryChangeEvent will be broadcast to all listeners
+ * interested on changes in the given plug-in.
+ * </p>
+ */
+ public void add(Contribution element) {
+ access.enterWrite();
+ try {
+ basicAdd(element, true);
+ fireRegistryChangeEvent();
+ } finally {
+ access.exitWrite();
+ }
+ }
+
+ public void add(Contribution[] elements) {
+ access.enterWrite();
+ try {
+ for (int i = 0; i < elements.length; i++)
+ basicAdd(elements[i], true);
+ fireRegistryChangeEvent();
+ } finally {
+ access.exitWrite();
+ }
+ }
+
+ /* Utility method to help with array concatenations */
+ static Object concatArrays(Object a, Object b) {
+ Object[] result = (Object[]) Array.newInstance(a.getClass().getComponentType(), Array.getLength(a) + Array.getLength(b));
+ System.arraycopy(a, 0, result, 0, Array.getLength(a));
+ System.arraycopy(b, 0, result, Array.getLength(a), Array.getLength(b));
+ return result;
+ }
+
+ private String addExtension(int extension) {
+ Extension addedExtension = (Extension) registryObjects.getObject(extension, RegistryObjectManager.EXTENSION);
+ String extensionPointToAddTo = addedExtension.getExtensionPointIdentifier();
+ ExtensionPoint extPoint = registryObjects.getExtensionPointObject(extensionPointToAddTo);
+ //orphan extension
+ if (extPoint == null) {
+ registryObjects.addOrphan(extensionPointToAddTo, extension);
+ return null;
+ }
+ // otherwise, link them
+ int[] newExtensions;
+ int[] existingExtensions = extPoint.getRawChildren();
+ newExtensions = new int[existingExtensions.length + 1];
+ System.arraycopy(existingExtensions, 0, newExtensions, 0, existingExtensions.length);
+ newExtensions[newExtensions.length - 1] = extension;
+ link(extPoint, newExtensions);
+ return recordChange(extPoint, extension, IExtensionDelta.ADDED);
+ }
+
+ /**
+ * Looks for existing orphan extensions to connect to the given extension
+ * point. If none is found, there is nothing to do. Otherwise, link them.
+ */
+ private String addExtensionPoint(int extPoint) {
+ ExtensionPoint extensionPoint = (ExtensionPoint) registryObjects.getObject(extPoint, RegistryObjectManager.EXTENSION_POINT);
+ int[] orphans = registryObjects.removeOrphans(extensionPoint.getUniqueIdentifier());
+ if (orphans == null)
+ return null;
+ link(extensionPoint, orphans);
+ return recordChange(extensionPoint, orphans, IExtensionDelta.ADDED);
+ }
+
+ private Set addExtensionsAndExtensionPoints(Contribution element) {
+ // now add and resolve extensions and extension points
+ Set affectedNamespaces = new HashSet();
+ int[] extPoints = element.getExtensionPoints();
+ for (int i = 0; i < extPoints.length; i++) {
+ String namespace = this.addExtensionPoint(extPoints[i]);
+ if (namespace != null)
+ affectedNamespaces.add(namespace);
+ }
+ int[] extensions = element.getExtensions();
+ for (int i = 0; i < extensions.length; i++) {
+ String namespace = this.addExtension(extensions[i]);
+ if (namespace != null)
+ affectedNamespaces.add(namespace);
+ }
+ return affectedNamespaces;
+ }
+
+ public void addRegistryChangeListener(EventListener listener) {
+ // this is just a convenience API - no need to do any sync'ing here
+ addRegistryChangeListener(listener, null);
+ }
+
+ public void addRegistryChangeListener(EventListener listener, String filter) {
+ synchronized (listeners) {
+ listeners.add(new ListenerInfo(listener, filter));
+ }
+ }
+
+ private void basicAdd(Contribution element, boolean link) {
+ // ignore anonymous namespaces
+ if (element.getNamespace() == null)
+ return;
+
+ registryObjects.addContribution(element);
+ if (!link)
+ return;
+
+ Set affectedNamespaces = addExtensionsAndExtensionPoints(element);
+ setObjectManagers(affectedNamespaces, registryObjects.createDelegatingObjectManager(registryObjects.getAssociatedObjects(element.getContributorId())));
+ }
+
+ private void setObjectManagers(Set affectedNamespaces, IObjectManager manager) {
+ for (Iterator iter = affectedNamespaces.iterator(); iter.hasNext();) {
+ getDelta((String) iter.next()).setObjectManager(manager);
+ }
+ }
+
+ private void basicRemove(long contributorId) {
+ // ignore anonymous namespaces
+ Set affectedNamespaces = removeExtensionsAndExtensionPoints(contributorId);
+ Map associatedObjects = registryObjects.getAssociatedObjects(contributorId);
+ registryObjects.removeObjects(associatedObjects);
+ registryObjects.addNavigableObjects(associatedObjects); // put the complete set of navigable objects
+ setObjectManagers(affectedNamespaces, registryObjects.createDelegatingObjectManager(associatedObjects));
+
+ registryObjects.removeContribution(contributorId);
+ }
+
+ // allow other objects in the registry to use the same lock
+ void enterRead() {
+ access.enterRead();
+ }
+
+ // allow other objects in the registry to use the same lock
+ void exitRead() {
+ access.exitRead();
+ }
+
+ /**
+ * Broadcasts (asynchronously) the event to all interested parties.
+ */
+ private void fireRegistryChangeEvent() {
+ // if there is nothing to say, just bail out
+ if (deltas.isEmpty() || listeners.isEmpty())
+ return;
+ // for thread safety, create tmp collections
+ Object[] tmpListeners = listeners.getListeners();
+ Map tmpDeltas = new HashMap(this.deltas);
+ // the deltas have been saved for notification - we can clear them now
+ deltas.clear();
+ // do the notification asynchronously
+ strategy.scheduleChangeEvent(tmpListeners, tmpDeltas, this);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.eclipse.core.runtime.IExtensionRegistry#getConfigurationElementsFor(java.lang.String)
+ */
+ public IConfigurationElement[] getConfigurationElementsFor(String extensionPointId) {
+ // this is just a convenience API - no need to do any sync'ing here
+ int lastdot = extensionPointId.lastIndexOf('.');
+ if (lastdot == -1)
+ return new IConfigurationElement[0];
+ return getConfigurationElementsFor(extensionPointId.substring(0, lastdot), extensionPointId.substring(lastdot + 1));
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.eclipse.core.runtime.IExtensionRegistry#getConfigurationElementsFor(java.lang.String, java.lang.String)
+ */
+ public IConfigurationElement[] getConfigurationElementsFor(String pluginId, String extensionPointSimpleId) {
+ // this is just a convenience API - no need to do any sync'ing here
+ IExtensionPoint extPoint = this.getExtensionPoint(pluginId, extensionPointSimpleId);
+ if (extPoint == null)
+ return new IConfigurationElement[0];
+ return extPoint.getConfigurationElements();
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.eclipse.core.runtime.IExtensionRegistry#getConfigurationElementsFor(java.lang.String, java.lang.String, java.lang.String)
+ */
+ public IConfigurationElement[] getConfigurationElementsFor(String pluginId, String extensionPointName, String extensionId) {
+ // this is just a convenience API - no need to do any sync'ing here
+ IExtension extension = this.getExtension(pluginId, extensionPointName, extensionId);
+ if (extension == null)
+ return new IConfigurationElement[0];
+ return extension.getConfigurationElements();
+ }
+
+ private RegistryDelta getDelta(String namespace) {
+ // is there a delta for the plug-in?
+ RegistryDelta existingDelta = (RegistryDelta) deltas.get(namespace);
+ if (existingDelta != null)
+ return existingDelta;
+
+ //if not, create one
+ RegistryDelta delta = new RegistryDelta();
+ deltas.put(namespace, delta);
+ return delta;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.eclipse.core.runtime.IExtensionRegistry#getExtension(java.lang.String)
+ */
+ public IExtension getExtension(String extensionId) {
+ if (extensionId == null)
+ return null;
+ int lastdot = extensionId.lastIndexOf('.');
+ if (lastdot == -1)
+ return null;
+ String namespace = extensionId.substring(0, lastdot);
+
+ long[] contributorIds = getContributorIds(namespace);
+ for (int i = 0; i < contributorIds.length; i++) {
+ int[] extensions = registryObjects.getExtensionsFrom(contributorIds[i]);
+ for (int j = 0; j < extensions.length; j++) {
+ Extension ext = (Extension) registryObjects.getObject(extensions[j], RegistryObjectManager.EXTENSION);
+ if (extensionId.equals(ext.getUniqueIdentifier()) && registryObjects.getExtensionPointObject(ext.getExtensionPointIdentifier()) != null) {
+ return (IExtension) registryObjects.getHandle(extensions[j], RegistryObjectManager.EXTENSION);
+ }
+ }
+
+ }
+ return null;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.eclipse.core.runtime.IExtensionRegistry#getExtension(java.lang.String, java.lang.String)
+ */
+ public IExtension getExtension(String extensionPointId, String extensionId) {
+ // this is just a convenience API - no need to do any sync'ing here
+ int lastdot = extensionPointId.lastIndexOf('.');
+ if (lastdot == -1)
+ return null;
+ return getExtension(extensionPointId.substring(0, lastdot), extensionPointId.substring(lastdot + 1), extensionId);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.eclipse.core.runtime.IExtensionRegistry#getExtension(java.lang.String, java.lang.String, java.lang.String)
+ */
+ public IExtension getExtension(String pluginId, String extensionPointName, String extensionId) {
+ // this is just a convenience API - no need to do any sync'ing here
+ IExtensionPoint extPoint = getExtensionPoint(pluginId, extensionPointName);
+ if (extPoint != null)
+ return extPoint.getExtension(extensionId);
+ return null;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.eclipse.core.runtime.IExtensionRegistry#getExtensionPoint(java.lang.String)
+ */
+ public IExtensionPoint getExtensionPoint(String xptUniqueId) {
+ return registryObjects.getExtensionPointHandle(xptUniqueId);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.eclipse.core.runtime.IExtensionRegistry#getExtensionPoint(java.lang.String, java.lang.String)
+ */
+ public IExtensionPoint getExtensionPoint(String elementName, String xpt) {
+ access.enterRead();
+ try {
+ return registryObjects.getExtensionPointHandle(elementName + '.' + xpt);
+ } finally {
+ access.exitRead();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.eclipse.core.runtime.IExtensionRegistry#getExtensionPoints()
+ */
+ public IExtensionPoint[] getExtensionPoints() {
+ access.enterRead();
+ try {
+ return registryObjects.getExtensionPointsHandles();
+ } finally {
+ access.exitRead();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.eclipse.core.runtime.IExtensionRegistry#getExtensionPoints(java.lang.String)
+ */
+ public IExtensionPoint[] getExtensionPoints(String namespace) {
+ access.enterRead();
+ try {
+ long[] contributorIds = getContributorIds(namespace);
+ IExtensionPoint[] result = ExtensionPointHandle.EMPTY_ARRAY;
+ for (int i = 0; i < contributorIds.length; i++) {
+ result = (IExtensionPoint[]) concatArrays(result, registryObjects.getHandles(registryObjects.getExtensionPointsFrom(contributorIds[i]), RegistryObjectManager.EXTENSION_POINT));
+ }
+ return result;
+ } finally {
+ access.exitRead();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.eclipse.core.runtime.IExtensionRegistry#getExtensions(java.lang.String)
+ */
+ public IExtension[] getExtensions(String namespace) {
+ access.enterRead();
+ try {
+ long[] contributorIds = getContributorIds(namespace);
+ List tmp = new ArrayList();
+ for (int i = 0; i < contributorIds.length; i++) {
+ Extension[] exts = (Extension[]) registryObjects.getObjects(registryObjects.getExtensionsFrom(contributorIds[i]), RegistryObjectManager.EXTENSION);
+ for (int j = 0; j < exts.length; j++) {
+ if (registryObjects.getExtensionPointObject(exts[j].getExtensionPointIdentifier()) != null)
+ tmp.add(registryObjects.getHandle(exts[j].getObjectId(), RegistryObjectManager.EXTENSION));
+ }
+ }
+ if (tmp.size() == 0)
+ return ExtensionHandle.EMPTY_ARRAY;
+ IExtension[] result = new IExtension[tmp.size()];
+ return (IExtension[]) tmp.toArray(result);
+ } finally {
+ access.exitRead();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.eclipse.core.runtime.IExtensionRegistry#getNamespaces()
+ */
+ public String[] getNamespaces() {
+ access.enterRead();
+ try {
+ Set namespaces = registryObjects.getNamespaces();
+ String[] result = new String[namespaces.size()];
+ return (String[]) namespaces.toArray(result);
+ } finally {
+ access.exitRead();
+ }
+ }
+
+ public boolean hasNamespace(long name) {
+ access.enterRead();
+ try {
+ return registryObjects.hasContribution(name);
+ } finally {
+ access.exitRead();
+ }
+ }
+
+ private void link(ExtensionPoint extPoint, int[] extensions) {
+ extPoint.setRawChildren(extensions);
+ registryObjects.add(extPoint, true);
+ }
+
+ /*
+ * Records an extension addition/removal.
+ */
+ private String recordChange(ExtensionPoint extPoint, int extension, int kind) {
+ // avoid computing deltas when there are no listeners
+ if (listeners.isEmpty())
+ return null;
+ ExtensionDelta extensionDelta = new ExtensionDelta();
+ extensionDelta.setExtension(extension);
+ extensionDelta.setExtensionPoint(extPoint.getObjectId());
+ extensionDelta.setKind(kind);
+ getDelta(extPoint.getNamespace()).addExtensionDelta(extensionDelta);
+ return extPoint.getNamespace();
+ }
+
+ /*
+ * Records a set of extension additions/removals.
+ */
+ private String recordChange(ExtensionPoint extPoint, int[] extensions, int kind) {
+ if (listeners.isEmpty())
+ return null;
+ if (extensions == null || extensions.length == 0)
+ return null;
+ RegistryDelta pluginDelta = getDelta(extPoint.getNamespace());
+ for (int i = 0; i < extensions.length; i++) {
+ ExtensionDelta extensionDelta = new ExtensionDelta();
+ extensionDelta.setExtension(extensions[i]);
+ extensionDelta.setExtensionPoint(extPoint.getObjectId());
+ extensionDelta.setKind(kind);
+ pluginDelta.addExtensionDelta(extensionDelta);
+ }
+ return extPoint.getNamespace();
+ }
+
+ /**
+ * Unresolves and removes all extensions and extension points provided by
+ * the plug-in.
+ * <p>
+ * A corresponding IRegistryChangeEvent will be broadcast to all listeners
+ * interested on changes in the given plug-in.
+ * </p>
+ */
+ public void remove(long removedContributorId) {
+ access.enterWrite();
+ try {
+ basicRemove(removedContributorId);
+ fireRegistryChangeEvent();
+ } finally {
+ access.exitWrite();
+ }
+ }
+
+ //Return the affected namespace
+ private String removeExtension(int extensionId) {
+ Extension extension = (Extension) registryObjects.getObject(extensionId, RegistryObjectManager.EXTENSION);
+ String xptName = extension.getExtensionPointIdentifier();
+ ExtensionPoint extPoint = registryObjects.getExtensionPointObject(xptName);
+ if (extPoint == null) {
+ registryObjects.removeOrphan(xptName, extensionId);
+ return null;
+ }
+ // otherwise, unlink the extension from the extension point
+ int[] existingExtensions = extPoint.getRawChildren();
+ int[] newExtensions = RegistryObjectManager.EMPTY_INT_ARRAY;
+ if (existingExtensions.length > 1) {
+ if (existingExtensions.length == 1)
+ newExtensions = RegistryObjectManager.EMPTY_INT_ARRAY;
+
+ newExtensions = new int[existingExtensions.length - 1];
+ for (int i = 0, j = 0; i < existingExtensions.length; i++)
+ if (existingExtensions[i] != extension.getObjectId())
+ newExtensions[j++] = existingExtensions[i];
+ }
+ link(extPoint, newExtensions);
+ return recordChange(extPoint, extension.getObjectId(), IExtensionDelta.REMOVED);
+ }
+
+ private String removeExtensionPoint(int extPoint) {
+ ExtensionPoint extensionPoint = (ExtensionPoint) registryObjects.getObject(extPoint, RegistryObjectManager.EXTENSION_POINT);
+ int[] existingExtensions = extensionPoint.getRawChildren();
+ if (existingExtensions == null || existingExtensions.length == 0) {
+ return null;
+ }
+ //Remove the extension point from the registry object
+ registryObjects.addOrphans(extensionPoint.getUniqueIdentifier(), existingExtensions);
+ link(extensionPoint, RegistryObjectManager.EMPTY_INT_ARRAY);
+ return recordChange(extensionPoint, existingExtensions, IExtensionDelta.REMOVED);
+ }
+
+ private Set removeExtensionsAndExtensionPoints(long contributorId) {
+ Set affectedNamespaces = new HashSet();
+ int[] extensions = registryObjects.getExtensionsFrom(contributorId);
+ for (int i = 0; i < extensions.length; i++) {
+ String namespace = this.removeExtension(extensions[i]);
+ if (namespace != null)
+ affectedNamespaces.add(namespace);
+ }
+
+ // remove extension points
+ int[] extPoints = registryObjects.getExtensionPointsFrom(contributorId);
+ for (int i = 0; i < extPoints.length; i++) {
+ String namespace = this.removeExtensionPoint(extPoints[i]);
+ if (namespace != null)
+ affectedNamespaces.add(namespace);
+ }
+ return affectedNamespaces;
+ }
+
+ public void removeRegistryChangeListener(EventListener listener) {
+ synchronized (listeners) {
+ listeners.remove(new ListenerInfo(listener, null));
+ }
+ }
+
+ /**
+ * @param strategy - optional strategy that modify the behaviour of the extension registry.
+ * Might be null.
+ * @param key - the key token supplied by the owner of the registry. The same token should be
+ * passed to access-controlled methods of the registry.
+ */
+ public ExtensionRegistry(RegistryStrategy registryStrategy, Object key) {
+ if (registryStrategy != null)
+ strategy = registryStrategy;
+ else
+ strategy = new RegistryStrategy(null, true);
+ // split strategies - reduce number of "instanceof" calls
+ if (registryStrategy instanceof ICompatibilityStrategy)
+ compatibilityStrategy = (ICompatibilityStrategy) strategy;
+ // create the file manager right away
+ setFileManager(strategy.getStorage(), strategy.isCacheReadOnly());
+
+ token = key;
+ registryObjects = new RegistryObjectManager(this);
+
+ if (strategy.cacheUse()) {
+ // Try to read the registry from the cache first. If that fails, create a new registry
+ long start = 0;
+ if (debug())
+ start = System.currentTimeMillis();
+
+ //The cache is made of several files, find the real names of these other files. If all files are found, try to initialize the objectManager
+ if (checkCache()) {
+ try {
+ theTableReader.setTableFile(cacheFileManager.lookup(TableReader.TABLE, false));
+ theTableReader.setExtraDataFile(cacheFileManager.lookup(TableReader.EXTRA, false));
+ theTableReader.setMainDataFile(cacheFileManager.lookup(TableReader.MAIN, false));
+ theTableReader.setContributionsFile(cacheFileManager.lookup(TableReader.CONTRIBUTIONS, false));
+ theTableReader.setOrphansFile(cacheFileManager.lookup(TableReader.ORPHANS, false));
+ isRegistryFilledFromCache = registryObjects.init(computeTimeStamp());
+ } catch (IOException e) {
+ // Ignore the exception. The registry will be rebuilt from the xml files.
+ }
+ }
+
+ if (debug() && isRegistryFilledFromCache)
+ System.out.println("Reading registry cache: " + (System.currentTimeMillis() - start)); //$NON-NLS-1$
+
+ if (debug()) {
+ if (!isRegistryFilledFromCache)
+ System.out.println("Reloading registry from manifest files..."); //$NON-NLS-1$
+ else
+ System.out.println("Using registry cache..."); //$NON-NLS-1$
+ }
+ }
+
+ if (debugEvents())
+ addRegistryChangeListener(new IRegistryChangeListener() {
+ public void registryChanged(IRegistryChangeEvent event) {
+ System.out.println(event);
+ }
+ });
+
+ // Do extra start processing if specified in the registry strategy
+ strategy.onStart(this);
+ }
+
+ /**
+ * Stops the registry. Registry has to be stopped to properly
+ * close cache and dispose of listeners.
+ * @param key - key token for this registry
+ */
+ public void stop(Object key) {
+ // If the registry creator specified a key token, check that the key mathches it
+ // (it is assumed that registry owner keeps the key to prevent unautorized accesss).
+ if (token != null && token != key) {
+ throw new IllegalArgumentException("Unauthorized access to the ExtensionRegistry.stop() method. Check if proper access token is supplied."); //$NON-NLS-1$
+ }
+
+ // Do extra stop processing if specified in the registry strategy
+ strategy.onStop(this);
+
+ stopChangeEventScheduler();
+
+ if (cacheFileManager == null)
+ return;
+
+ if (!registryObjects.isDirty() || cacheFileManager.isReadOnly()) {
+ cacheFileManager.close();
+ return;
+ }
+
+ File tableFile = null;
+ File mainFile = null;
+ File extraFile = null;
+ File contributionsFile = null;
+ File orphansFile = null;
+
+ TableWriter theTableWriter = new TableWriter(this);
+
+ try {
+ cacheFileManager.lookup(TableReader.TABLE, true);
+ cacheFileManager.lookup(TableReader.MAIN, true);
+ cacheFileManager.lookup(TableReader.EXTRA, true);
+ cacheFileManager.lookup(TableReader.CONTRIBUTIONS, true);
+ cacheFileManager.lookup(TableReader.ORPHANS, true);
+ tableFile = File.createTempFile(TableReader.TABLE, ".new", cacheFileManager.getBase()); //$NON-NLS-1$
+ mainFile = File.createTempFile(TableReader.MAIN, ".new", cacheFileManager.getBase()); //$NON-NLS-1$
+ extraFile = File.createTempFile(TableReader.EXTRA, ".new", cacheFileManager.getBase()); //$NON-NLS-1$
+ contributionsFile = File.createTempFile(TableReader.CONTRIBUTIONS, ".new", cacheFileManager.getBase()); //$NON-NLS-1$
+ orphansFile = File.createTempFile(TableReader.ORPHANS, ".new", cacheFileManager.getBase()); //$NON-NLS-1$
+ theTableWriter.setTableFile(tableFile);
+ theTableWriter.setExtraDataFile(extraFile);
+ theTableWriter.setMainDataFile(mainFile);
+ theTableWriter.setContributionsFile(contributionsFile);
+ theTableWriter.setOrphansFile(orphansFile);
+ } catch (IOException e) {
+ cacheFileManager.close();
+ return; //Ignore the exception since we can recompute the cache
+ }
+ try {
+ if (theTableWriter.saveCache(registryObjects, computeTimeStamp()))
+ cacheFileManager.update(new String[] {TableReader.TABLE, TableReader.MAIN, TableReader.EXTRA, TableReader.CONTRIBUTIONS, TableReader.ORPHANS}, new String[] {tableFile.getName(), mainFile.getName(), extraFile.getName(), contributionsFile.getName(), orphansFile.getName()});
+ } catch (IOException e) {
+ //Ignore the exception since we can recompute the cache
+ }
+
+ cacheFileManager.close();
+ }
+
+ /*
+ * Clear the registry cache files from the file manager so on next start-up we recompute it.
+ */
+ public void clearRegistryCache() {
+ String[] keys = new String[] {TableReader.TABLE, TableReader.MAIN, TableReader.EXTRA, TableReader.CONTRIBUTIONS, TableReader.ORPHANS};
+ for (int i = 0; i < keys.length; i++)
+ try {
+ cacheFileManager.remove(keys[i]);
+ } catch (IOException e) {
+ log(new Status(IStatus.ERROR, RegistryMessages.OWNER_NAME, IStatus.ERROR, RegistryMessages.meta_registryCacheReadProblems, e));
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////////////////////////////////
+ // Registry Object Factory
+ // The factory produces contributions, extension points, extensions, and configuration elements
+ // to be stored in the extension registry.
+ protected RegistryObjectFactory theRegistryObjectFactory = null;
+
+ // Override to provide domain-specific elements to be stored in the extension registry
+ protected void setElementFactory() {
+ theRegistryObjectFactory = new RegistryObjectFactory(this);
+ }
+
+ // Lazy initialization.
+ public RegistryObjectFactory getElementFactory() {
+ if (theRegistryObjectFactory == null)
+ setElementFactory();
+ return theRegistryObjectFactory;
+ }
+
+ TableReader getCleanTableReader() {
+ theTableReader.reset();
+ return theTableReader;
+ }
+
+ public void log(IStatus status) {
+ strategy.log(status);
+ }
+
+ public void setInitializationData(Object newClassInstance, IConfigurationElement confElement, String propertyName, Object initData) throws CoreException {
+ if (compatibilityStrategy != null)
+ compatibilityStrategy.setInitializationData(newClassInstance, confElement, propertyName, initData);
+ }
+
+ public String translate(String key, ResourceBundle resources) {
+ return strategy.translate(key, resources);
+ }
+
+ public boolean debug() {
+ return strategy.debug();
+ }
+
+ public boolean debugEvents() {
+ return strategy.debugRegistryEvents();
+ }
+
+ public boolean useLazyCacheLoading() {
+ return strategy.cacheLazyLoading();
+ }
+
+ public long computeState() {
+ return strategy.cacheComputeState();
+ }
+
+ public long computeTimeStamp() {
+ return strategy.cacheComputeTimeStamp();
+ }
+
+ // Check that cache is actually present in the specified location
+ protected boolean checkCache() {
+ File cacheFile = null;
+ if (cacheFileManager != null) {
+ try {
+ cacheFile = cacheFileManager.lookup(TableReader.getTestFileName(), false);
+ } catch (IOException e) {
+ //Ignore the exception. The registry will be rebuilt from the xml files.
+ }
+ }
+ if (cacheFile != null && cacheFile.isFile())
+ return true; // primary location is fine
+
+ // check alternative cache location if available
+ File alternativeBase = strategy.cacheAlternativeLocation();
+ if (alternativeBase != null) {
+ setFileManager(alternativeBase, true);
+ if (cacheFileManager != null) {
+ // check this new location:
+ cacheFile = null;
+ try {
+ cacheFile = cacheFileManager.lookup(TableReader.getTestFileName(), false);
+ } catch (IOException e) {
+ //Ignore the exception. The registry will be rebuilt from the xml files.
+ }
+ return (cacheFile != null && cacheFile.isFile());
+ }
+ }
+ return false;
+ }
+
+ public boolean filledFromCache() {
+ return isRegistryFilledFromCache;
+ }
+
+ public Object processExecutableExtension(String contributorName, long namespaceOwnerId, String namespaceName, String className, Object initData, String propertyName, ConfigurationElement confElement) throws CoreException {
+ ConfigurationElementHandle confElementHandle = new ConfigurationElementHandle(getObjectManager(), confElement.getObjectId());
+ return strategy.createExecutableExtension(contributorName, namespaceOwnerId, namespaceName, className, initData, propertyName, confElementHandle);
+ }
+
+ //////////////////////////////////////////////////////////////////////////////////////////
+ // Registry namespace resolution
+
+ public long getNamespaceOwnerId(long contributorId) {
+ return strategy.getNamespaceOwnerId(contributorId);
+ }
+
+ public String getNamespace(long contributorId) {
+ return strategy.getNamespace(contributorId);
+ }
+
+ public long[] getContributorIds(String namespace) {
+ return strategy.getNamespaceContributors(namespace);
+ }
+
+ //////////////////////////////////////////////////////////////////////////////////////////
+ // Registry change events processing
+
+ public IStatus processChangeEvent(Object[] listenerInfos, Map deltas) {
+ MultiStatus result = new MultiStatus(RegistryMessages.OWNER_NAME, IStatus.OK, RegistryMessages.plugin_eventListenerError, null);
+ for (int i = 0; i < listenerInfos.length; i++) {
+ ListenerInfo listenerInfo = (ListenerInfo) listenerInfos[i];
+ if (listenerInfo.filter != null && !deltas.containsKey(listenerInfo.filter))
+ continue;
+ if (listenerInfo.listener instanceof IRegistryChangeListener)
+ ((IRegistryChangeListener) listenerInfo.listener).registryChanged(new RegistryChangeEvent(deltas, listenerInfo.filter));
+ if (compatibilityStrategy != null)
+ compatibilityStrategy.invokeListener(listenerInfo.listener, deltas, listenerInfo.filter);
+ }
+ for (Iterator iter = deltas.values().iterator(); iter.hasNext();) {
+ ((RegistryDelta) iter.next()).getObjectManager().close();
+ }
+ return result;
+ }
+
+ private RegistryEventThread eventThread = null; // registry event loop
+ private final List queue = new LinkedList(); // stores registry events info
+
+ // Registry events notifications are done on a separate thread in a sequential manner
+ // (first in - first processed)
+ public void scheduleChangeEvent(Object[] listenerInfos, Map deltas) {
+ QueueElement newElement = new QueueElement(listenerInfos, deltas);
+ if (eventThread == null) {
+ eventThread = new RegistryEventThread(this);
+ eventThread.start();
+ }
+ synchronized (queue) {
+ queue.add(newElement);
+ queue.notify();
+ }
+ }
+
+ // The pair of values we store in the event queue
+ private class QueueElement {
+ Object[] listenerInfos;
+ Map deltas;
+
+ QueueElement(Object[] infos, Map deltas) {
+ this.deltas = deltas;
+ listenerInfos = infos;
+ }
+ }
+
+ private class RegistryEventThread extends Thread {
+ private ExtensionRegistry registry;
+
+ public RegistryEventThread(ExtensionRegistry registry) {
+ super("Extension Registry Event Dispatcher"); //$NON-NLS-1$
+ setDaemon(true);
+ this.registry = registry;
+ }
+
+ public void run() {
+ while (true) {
+ QueueElement element;
+ synchronized (queue) {
+ try {
+ while (queue.isEmpty())
+ queue.wait();
+ } catch (InterruptedException e) {
+ return;
+ }
+ element = (QueueElement) queue.remove(0);
+ }
+ registry.processChangeEvent(element.listenerInfos, element.deltas);
+ }
+ }
+ }
+
+ protected void stopChangeEventScheduler() {
+ if (eventThread != null) {
+ synchronized (queue) {
+ eventThread.interrupt();
+ eventThread = null;
+ }
+ }
+ }
+
+ //////////////////////////////////////////////////////////////////////////////////////
+ // Modifiable portion
+
+ /* (non-Javadoc)
+ * @see org.eclipse.equinox.registry.ISPIExtensionRegistry#addXMLContribution(java.io.InputStream, long, javax.xml.parsers.SAXParserFactory)
+ */
+ public boolean addXMLContribution(InputStream is, long contributorId, String contributionType, String contributionName, ResourceBundle b, Object key) {
+ // If the registry is not modifiable, check that the proper key was passed in
+ if (!strategy.isModifiable() && token != key) {
+ throw new IllegalArgumentException("Unauthorized access to the ExtensionRegistry.addXMLContribution() method. Check if proper access token is supplied."); //$NON-NLS-1$
+ }
+
+ String ownerName = getNamespace(contributorId);
+ String message = NLS.bind(RegistryMessages.parse_problems, ownerName);
+ MultiStatus problems = new MultiStatus(RegistryMessages.OWNER_NAME, ExtensionsParser.PARSE_PROBLEM, message, null);
+ ExtensionsParser parser = new ExtensionsParser(problems, this);
+ Contribution contribution = getElementFactory().createContribution(contributorId);
+
+ try {
+ parser.parseManifest(strategy.getXMLParser(), new InputSource(is), contributionType, contributionName, getObjectManager(), contribution, b);
+ if (problems.getSeverity() != IStatus.OK) {
+ log(problems);
+ return false;
+ }
+ } catch (ParserConfigurationException e) {
+ logError(ownerName, contributionName, e);
+ return false;
+ } catch (SAXException e) {
+ logError(ownerName, contributionName, e);
+ return false;
+ } catch (IOException e) {
+ logError(ownerName, contributionName, e);
+ return false;
+ } finally {
+ try {
+ is.close();
+ } catch (IOException ioe) {
+ // nothing to do
+ }
+ }
+
+ // Do not synchronize on registry here because the registry handles
+ // the synchronization for us in registry.add
+ add(contribution);
+ return true;
+ }
+
+ private void logError(String owner, String contributionName, Exception e) {
+ String message = NLS.bind(RegistryMessages.parse_failedParsingManifest, owner + "/" + contributionName); //$NON-NLS-1$
+ log(new Status(IStatus.ERROR, RegistryMessages.OWNER_NAME, 0, message, e));
+ }
+
+ /**
+ * Creates an extension point.
+ *
+ * @param contributorId - Id of the supplier of this extension point
+ * @param extensionPointId - Id of the extension point. If non-qualified names is supplied,
+ * it will be converted internally into a fully qualified name.
+ * @param extensionPointLabel- display string for the extension point
+ * @param schemaLocation - points to the location of the XML schema file
+ */
+ public void createExtensionPoint(long contributorId, String extensionPointId, String extensionPointLabel, String schemaLocation) {
+
+ // Extension point Id might not be null
+ if (extensionPointId == null) {
+ String message = NLS.bind(RegistryMessages.create_failedExtensionPoint, extensionPointLabel);
+ log(new Status(IStatus.ERROR, RegistryMessages.OWNER_NAME, 0, message, null));
+ }
+
+ // prepare namespace information
+ String namespaceName = getNamespace(contributorId);
+ long namespaceOwnerId = getNamespaceOwnerId(contributorId);
+
+ // addition wraps in a contribution
+ Contribution contribution = getElementFactory().createContribution(contributorId);
+
+ ExtensionPoint currentExtPoint = getElementFactory().createExtensionPoint();
+ String uniqueId = namespaceName + '.' + extensionPointId;
+ currentExtPoint.setUniqueIdentifier(uniqueId);
+ String labelNLS = translate(extensionPointLabel, null);
+ currentExtPoint.setLabel(labelNLS);
+ currentExtPoint.setSchema(schemaLocation);
+
+ getObjectManager().addExtensionPoint(currentExtPoint, true);
+
+ currentExtPoint.setNamespace(namespaceName);
+ currentExtPoint.setNamespaceOwnerId(namespaceOwnerId);
+
+ // array format: {Number of extension points, Number of extensions, Extension Id}
+ int[] contributionChildren = new int[3];
+ // Put the extension points into this namespace
+ contributionChildren[Contribution.EXTENSION_POINT] = 1;
+ contributionChildren[Contribution.EXTENSION] = 0;
+ contributionChildren[Contribution.EXTENSION + 1] = currentExtPoint.getObjectId();
+
+ contribution.setRawChildren(contributionChildren);
+
+ add(contribution);
+ }
+
+ /**
+ * Creates an extension.
+ *
+ * @see ExtensionDescription
+ *
+ * @param contributorId - Id of the supplier of this extension
+ * @param extensionId - Id of the extension. If non-qualified name is supplied,
+ * it will be converted internally into a fully qualified name
+ * @param extensionLabel - display string for this extension
+ * @param extensionPointId - Id of the point being extended. If non-qualified
+ * name is supplied, it is assumed to have the same contributorId as this extension
+ * @param description - contents of the extension
+ */
+ public void createExtension(long contributorId, String extensionId, String extensionLabel, String extensionPointId, ExtensionDescription description) {
+ // prepare namespace information
+ String namespaceName = getNamespace(contributorId);
+ long namespaceOwnerId = getNamespaceOwnerId(contributorId);
+
+ // addition wraps in a contribution
+ Contribution contribution = getElementFactory().createContribution(contributorId);
+
+ Extension currentExtension = getElementFactory().createExtension();
+ currentExtension.setSimpleIdentifier(extensionId);
+ String extensionLabelNLS = translate(extensionLabel, null);
+ currentExtension.setLabel(extensionLabelNLS);
+
+ String targetExtensionPointId;
+ if (extensionPointId.indexOf('.') == -1) // No dots -> namespace name added at the start
+ targetExtensionPointId = namespaceName + '.' + extensionPointId;
+ else
+ targetExtensionPointId = extensionPointId;
+ currentExtension.setExtensionPointIdentifier(targetExtensionPointId);
+
+ getObjectManager().add(currentExtension, true);
+
+ createExtensionData(namespaceOwnerId, description, currentExtension);
+
+ currentExtension.setNamespace(namespaceName);
+
+ int[] contributionChildren = new int[3];
+
+ contributionChildren[Contribution.EXTENSION_POINT] = 0;
+ contributionChildren[Contribution.EXTENSION] = 1;
+ contributionChildren[Contribution.EXTENSION + 1] = currentExtension.getObjectId();
+ contribution.setRawChildren(contributionChildren);
+
+ add(contribution);
+ }
+
+ // Fill in the actual content of this extension
+ private void createExtensionData(long namespaceOwnerId, ExtensionDescription description, RegistryObject parent) {
+ ConfigurationElement currentConfigurationElement = getElementFactory().createConfigurationElement();
+ currentConfigurationElement.setNamespaceOwnerId(namespaceOwnerId);
+ currentConfigurationElement.setName(description.getElementName());
+
+ if (description.hasProperties()) {
+ ExtensionProperty[] descriptionProperties = description.getProperties();
+ int len = descriptionProperties.length;
+
+ String[] properties = new String[len * 2];
+ for (int i = 0; i < len; i++) {
+ properties[i * 2] = descriptionProperties[i].getName();
+ properties[i * 2 + 1] = translate(descriptionProperties[i].getValue(), null);
+ }
+ currentConfigurationElement.setProperties(properties);
+ } else
+ currentConfigurationElement.setProperties(RegistryObjectManager.EMPTY_STRING_ARRAY);
+
+ String value = description.getValue();
+ if (value != null)
+ currentConfigurationElement.setValue(value);
+
+ getObjectManager().add(currentConfigurationElement, true);
+
+ // process children
+ ExtensionDescription[] children = description.getChildren();
+ if (children != null) {
+ for (int i = 0; i < children.length; i++) {
+ createExtensionData(namespaceOwnerId, children[i], currentConfigurationElement);
+ }
+ }
+
+ int[] oldValues = parent.getRawChildren();
+ int size = oldValues.length;
+ int[] newValues = new int[size + 1];
+ for (int i = 0; i < size; i++) {
+ newValues[i] = oldValues[i];
+ }
+ newValues[size] = currentConfigurationElement.getObjectId();
+ parent.setRawChildren(newValues);
+ currentConfigurationElement.setParentId(parent.getObjectId());
+ currentConfigurationElement.setParentType(parent instanceof ConfigurationElement ? RegistryObjectManager.CONFIGURATION_ELEMENT : RegistryObjectManager.EXTENSION);
+ }
+
+}
diff --git a/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/ExtensionsParser.java b/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/ExtensionsParser.java
new file mode 100644
index 000000000..f1b838d63
--- /dev/null
+++ b/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/ExtensionsParser.java
@@ -0,0 +1,592 @@
+/*******************************************************************************
+ * 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.registry;
+
+import java.io.IOException;
+import java.util.*;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParserFactory;
+import org.eclipse.core.runtime.*;
+import org.eclipse.osgi.util.NLS;
+import org.xml.sax.*;
+import org.xml.sax.helpers.DefaultHandler;
+
+public class ExtensionsParser extends DefaultHandler {
+ // Introduced for backward compatibility
+ private final static String NO_EXTENSION_MUNGING = "eclipse.noExtensionMunging"; //$NON-NLS-1$ //System property
+ private static Map extensionPointMap;
+
+ static {
+ initializeExtensionPointMap();
+ }
+
+ /**
+ * Initialize the list of renamed extension point ids
+ */
+ private static void initializeExtensionPointMap() {
+ Map map = new HashMap(13);
+ map.put("org.eclipse.ui.markerImageProvider", "org.eclipse.ui.ide.markerImageProvider"); //$NON-NLS-1$ //$NON-NLS-2$
+ map.put("org.eclipse.ui.markerHelp", "org.eclipse.ui.ide.markerHelp"); //$NON-NLS-1$ //$NON-NLS-2$
+ map.put("org.eclipse.ui.markerImageProviders", "org.eclipse.ui.ide.markerImageProviders"); //$NON-NLS-1$ //$NON-NLS-2$
+ map.put("org.eclipse.ui.markerResolution", "org.eclipse.ui.ide.markerResolution"); //$NON-NLS-1$ //$NON-NLS-2$
+ map.put("org.eclipse.ui.projectNatureImages", "org.eclipse.ui.ide.projectNatureImages"); //$NON-NLS-1$ //$NON-NLS-2$
+ map.put("org.eclipse.ui.resourceFilters", "org.eclipse.ui.ide.resourceFilters"); //$NON-NLS-1$ //$NON-NLS-2$
+ map.put("org.eclipse.ui.markerUpdaters", "org.eclipse.ui.editors.markerUpdaters"); //$NON-NLS-1$ //$NON-NLS-2$
+ map.put("org.eclipse.ui.documentProviders", "org.eclipse.ui.editors.documentProviders"); //$NON-NLS-1$ //$NON-NLS-2$
+ map.put("org.eclipse.ui.workbench.texteditor.markerAnnotationSpecification", "org.eclipse.ui.editors.markerAnnotationSpecification"); //$NON-NLS-1$ //$NON-NLS-2$
+ map.put("org.eclipse.help.browser", "org.eclipse.help.base.browser"); //$NON-NLS-1$ //$NON-NLS-2$
+ map.put("org.eclipse.help.luceneAnalyzer", "org.eclipse.help.base.luceneAnalyzer"); //$NON-NLS-1$ //$NON-NLS-2$
+ map.put("org.eclipse.help.webapp", "org.eclipse.help.base.webapp"); //$NON-NLS-1$ //$NON-NLS-2$
+ map.put("org.eclipse.help.support", "org.eclipse.ui.helpSupport"); //$NON-NLS-1$ //$NON-NLS-2$
+ extensionPointMap = map;
+ }
+
+ private static long cumulativeTime = 0;
+
+ // is in compatibility mode
+ private boolean compatibilityMode;
+
+ // File name for this extension manifest
+ // This to help with error reporting
+ private String locationName = null;
+
+ // Current State Information
+ private Stack stateStack = new Stack();
+
+ // Current object stack (used to hold the current object we are
+ // populating in this plugin descriptor
+ private Stack objectStack = new Stack();
+
+ private String schemaVersion = null;
+
+ // A status for holding results.
+ private MultiStatus status;
+
+ // Owning extension registry
+ private ExtensionRegistry registry;
+
+ // Resource bundle used to translate the content of the plugin.xml
+ protected ResourceBundle resources;
+
+ // Keep track of the object encountered.
+ private RegistryObjectManager objectManager;
+
+ private Contribution namespace;
+
+ //This keeps tracks of the value of the configuration element in case the value comes in several pieces (see characters()). See as well bug 75592.
+ private String configurationElementValue;
+
+ /**
+ * Status code constant (value 1) indicating a problem in a bundle extensions
+ * manifest (<code>extensions.xml</code>) file.
+ */
+ public static final int PARSE_PROBLEM = 1;
+
+ public static final String PLUGIN = "plugin"; //$NON-NLS-1$
+ public static final String PLUGIN_ID = "id"; //$NON-NLS-1$
+ public static final String PLUGIN_NAME = "name"; //$NON-NLS-1$
+ public static final String FRAGMENT = "fragment"; //$NON-NLS-1$
+ public static final String BUNDLE_UID = "id"; //$NON-NLS-1$
+
+ public static final String EXTENSION_POINT = "extension-point"; //$NON-NLS-1$
+ public static final String EXTENSION_POINT_NAME = "name"; //$NON-NLS-1$
+ public static final String EXTENSION_POINT_ID = "id"; //$NON-NLS-1$
+ public static final String EXTENSION_POINT_SCHEMA = "schema"; //$NON-NLS-1$
+
+ public static final String EXTENSION = "extension"; //$NON-NLS-1$
+ public static final String EXTENSION_NAME = "name"; //$NON-NLS-1$
+ public static final String EXTENSION_ID = "id"; //$NON-NLS-1$
+ public static final String EXTENSION_TARGET = "point"; //$NON-NLS-1$
+
+ public static final String ELEMENT = "element"; //$NON-NLS-1$
+ public static final String ELEMENT_NAME = "name"; //$NON-NLS-1$
+ public static final String ELEMENT_VALUE = "value"; //$NON-NLS-1$
+
+ public static final String PROPERTY = "property"; //$NON-NLS-1$
+ public static final String PROPERTY_NAME = "name"; //$NON-NLS-1$
+ public static final String PROPERTY_VALUE = "value"; //$NON-NLS-1$
+
+ // Valid States
+ private static final int IGNORED_ELEMENT_STATE = 0;
+ private static final int INITIAL_STATE = 1;
+ private static final int BUNDLE_STATE = 2;
+ private static final int BUNDLE_EXTENSION_POINT_STATE = 5;
+ private static final int BUNDLE_EXTENSION_STATE = 6;
+ private static final int CONFIGURATION_ELEMENT_STATE = 10;
+
+ // Keep a group of vectors as a temporary scratch space. These
+ // vectors will be used to populate arrays in the bundle model
+ // once processing of the XML file is complete.
+ private static final int EXTENSION_POINT_INDEX = 0;
+ private static final int EXTENSION_INDEX = 1;
+ private static final int LAST_INDEX = 1;
+
+ private ArrayList scratchVectors[] = new ArrayList[LAST_INDEX + 1];
+
+ private String manifestType;
+
+ private Locator locator = null;
+
+ public ExtensionsParser(MultiStatus status, ExtensionRegistry registry) {
+ super();
+ this.status = status;
+ this.registry = registry;
+ }
+
+ /**
+ * @see ContentHandler#setDocumentLocator
+ */
+ public void setDocumentLocator(Locator locator) {
+ this.locator = locator;
+ }
+
+ public void characters(char[] ch, int start, int length) {
+ int state = ((Integer) stateStack.peek()).intValue();
+ if (state != CONFIGURATION_ELEMENT_STATE)
+ return;
+ if (state == CONFIGURATION_ELEMENT_STATE) {
+ // Accept character data within an element, is when it is
+ // part of a configuration element (i.e. an element within an EXTENSION element
+ ConfigurationElement currentConfigElement = (ConfigurationElement) objectStack.peek();
+ String value = new String(ch, start, length);
+ if (configurationElementValue == null) {
+ if (value.trim().length() != 0) {
+ configurationElementValue = value;
+ }
+ } else {
+ configurationElementValue = configurationElementValue + value;
+ }
+ if (configurationElementValue != null)
+ currentConfigElement.setValue(translate(configurationElementValue));
+ }
+ }
+
+ public void endDocument() {
+ // do nothing
+ }
+
+ public void endElement(String uri, String elementName, String qName) {
+ switch (((Integer) stateStack.peek()).intValue()) {
+ case IGNORED_ELEMENT_STATE :
+ stateStack.pop();
+ break;
+ case INITIAL_STATE :
+ // shouldn't get here
+ internalError(NLS.bind(RegistryMessages.parse_internalStack, elementName));
+ break;
+ case BUNDLE_STATE :
+ if (elementName.equals(manifestType)) {
+ stateStack.pop();
+
+ ArrayList extensionPoints = scratchVectors[EXTENSION_POINT_INDEX];
+ ArrayList extensions = scratchVectors[EXTENSION_INDEX];
+ int[] namespaceChildren = new int[2 + extensionPoints.size() + extensions.size()];
+ int position = 2;
+ // Put the extension points into this namespace
+ if (extensionPoints.size() > 0) {
+ namespaceChildren[Contribution.EXTENSION_POINT] = extensionPoints.size();
+ for (Iterator iter = extensionPoints.iterator(); iter.hasNext();) {
+ namespaceChildren[position++] = ((RegistryObject) iter.next()).getObjectId();
+ }
+ extensionPoints.clear();
+ }
+
+ // Put the extensions into this namespace too
+ if (extensions.size() > 0) {
+ Extension[] renamedExtensions = fixRenamedExtensionPoints((Extension[]) extensions.toArray(new Extension[extensions.size()]));
+ namespaceChildren[Contribution.EXTENSION] = renamedExtensions.length;
+ for (int i = 0; i < renamedExtensions.length; i++) {
+ namespaceChildren[position++] = renamedExtensions[i].getObjectId();
+ }
+ extensions.clear();
+ }
+ namespace.setRawChildren(namespaceChildren);
+ }
+ break;
+ case BUNDLE_EXTENSION_POINT_STATE :
+ if (elementName.equals(EXTENSION_POINT)) {
+ stateStack.pop();
+ }
+ break;
+ case BUNDLE_EXTENSION_STATE :
+ if (elementName.equals(EXTENSION)) {
+ stateStack.pop();
+ // Finish up extension object
+ Extension currentExtension = (Extension) objectStack.pop();
+ currentExtension.setNamespace(namespace.getNamespace());
+ scratchVectors[EXTENSION_INDEX].add(currentExtension);
+ }
+ break;
+ case CONFIGURATION_ELEMENT_STATE :
+ // We don't care what the element name was
+ stateStack.pop();
+ // Now finish up the configuration element object
+ configurationElementValue = null;
+ ConfigurationElement currentConfigElement = (ConfigurationElement) objectStack.pop();
+
+ String value = currentConfigElement.getValueAsIs();
+ if (value != null) {
+ currentConfigElement.setValue(value.trim());
+ }
+
+ RegistryObject parent = (RegistryObject) objectStack.peek();
+ // Want to add this configuration element to the subelements of an extension
+ int[] oldValues = parent.getRawChildren();
+ int size = oldValues.length;
+ int[] newValues = new int[size + 1];
+ for (int i = 0; i < size; i++) {
+ newValues[i] = oldValues[i];
+ }
+ newValues[size] = currentConfigElement.getObjectId();
+ parent.setRawChildren(newValues);
+ currentConfigElement.setParentId(parent.getObjectId());
+ currentConfigElement.setParentType(parent instanceof ConfigurationElement ? RegistryObjectManager.CONFIGURATION_ELEMENT : RegistryObjectManager.EXTENSION);
+ break;
+ }
+ }
+
+ public void error(SAXParseException ex) {
+ logStatus(ex);
+ }
+
+ public void fatalError(SAXParseException ex) throws SAXException {
+ logStatus(ex);
+ throw ex;
+ }
+
+ private void handleExtensionPointState(String elementName) {
+ // We ignore all elements under extension points (if there are any)
+ stateStack.push(new Integer(IGNORED_ELEMENT_STATE));
+ unknownElement(EXTENSION_POINT, elementName);
+ }
+
+ private void handleExtensionState(String elementName, Attributes attributes) {
+ // You need to change the state here even though we will be executing the same
+ // code for ExtensionState and ConfigurationElementState. We ignore the name
+ // of the element for ConfigurationElements. When we are wrapping up, we will
+ // want to add each configuration element object to the subElements vector of
+ // its parent configuration element object. However, the first configuration
+ // element object we created (the last one we pop off the stack) will need to
+ // be added to a vector in the extension object called _configuration.
+ stateStack.push(new Integer(CONFIGURATION_ELEMENT_STATE));
+
+ configurationElementValue = null;
+
+ // create a new Configuration Element and push it onto the object stack
+ ConfigurationElement currentConfigurationElement = registry.getElementFactory().createConfigurationElement();
+ currentConfigurationElement.setNamespaceOwnerId(namespace.getNamespaceOwnerId());
+ objectStack.push(currentConfigurationElement);
+ currentConfigurationElement.setName(elementName);
+
+ // Processing the attributes of a configuration element involves creating
+ // a new configuration property for each attribute and populating the configuration
+ // property with the name/value pair of the attribute. Note there will be one
+ // configuration property for each attribute
+ parseConfigurationElementAttributes(attributes);
+ objectManager.add(currentConfigurationElement, true);
+ }
+
+ private void handleInitialState(String elementName, Attributes attributes) {
+ if (!elementName.equals(manifestType)) {
+ stateStack.push(new Integer(IGNORED_ELEMENT_STATE));
+ internalError(NLS.bind(RegistryMessages.parse_unknownTopElement, elementName));
+ return;
+ }
+ // new manifests should have the plugin (or fragment) element empty
+ // in compatibility mode, any extraneous elements will be silently ignored
+ compatibilityMode = attributes.getLength() > 0;
+ stateStack.push(new Integer(BUNDLE_STATE));
+ objectStack.push(namespace);
+ }
+
+ private void handleBundleState(String elementName, Attributes attributes) {
+ if (elementName.equals(EXTENSION_POINT)) {
+ stateStack.push(new Integer(BUNDLE_EXTENSION_POINT_STATE));
+ parseExtensionPointAttributes(attributes);
+ return;
+ }
+ if (elementName.equals(EXTENSION)) {
+ stateStack.push(new Integer(BUNDLE_EXTENSION_STATE));
+ parseExtensionAttributes(attributes);
+ return;
+ }
+
+ // If we get to this point, the element name is one we don't currently accept.
+ // Set the state to indicate that this element will be ignored
+ stateStack.push(new Integer(IGNORED_ELEMENT_STATE));
+ if (!compatibilityMode)
+ unknownElement(manifestType, elementName);
+ }
+
+ private void logStatus(SAXParseException ex) {
+ String name = ex.getSystemId();
+ if (name == null)
+ name = locationName;
+ if (name == null)
+ name = ""; //$NON-NLS-1$
+ else
+ name = name.substring(1 + name.lastIndexOf("/")); //$NON-NLS-1$
+
+ String msg;
+ if (name.equals("")) //$NON-NLS-1$
+ msg = NLS.bind(RegistryMessages.parse_error, ex.getMessage());
+ else
+ msg = NLS.bind(RegistryMessages.parse_errorNameLineColumn, (new Object[] {name, Integer.toString(ex.getLineNumber()), Integer.toString(ex.getColumnNumber()), ex.getMessage()}));
+ error(new Status(IStatus.WARNING, RegistryMessages.OWNER_NAME, PARSE_PROBLEM, msg, ex));
+ }
+
+ public Contribution parseManifest(SAXParserFactory factory, InputSource in, String manifestKind, String manifestName, RegistryObjectManager registryObjects, Contribution currentNamespace, ResourceBundle bundle) throws ParserConfigurationException, SAXException, IOException {
+ long start = 0;
+ this.resources = bundle;
+ this.objectManager = registryObjects;
+ //initialize the parser with this object
+ this.namespace = currentNamespace;
+ if (registry.debug())
+ start = System.currentTimeMillis();
+
+ if (factory == null)
+ throw new SAXException(RegistryMessages.parse_xmlParserNotAvailable);
+
+ try {
+ if (manifestKind == null)
+ throw new NullPointerException();
+ if (!(manifestKind.equals(PLUGIN) || manifestKind.equals(FRAGMENT)))
+ throw new IllegalArgumentException("Invalid manifest type: " + manifestType); //$NON-NLS-1$
+ this.manifestType = manifestKind;
+ locationName = in.getSystemId();
+ if (locationName == null)
+ locationName = manifestName;
+ factory.setNamespaceAware(true);
+ try {
+ factory.setFeature("http://xml.org/sax/features/string-interning", true); //$NON-NLS-1$
+ } catch (SAXException se) {
+ // ignore; we can still operate without string-interning
+ }
+ factory.setValidating(false);
+ factory.newSAXParser().parse(in, this);
+ return (Contribution) objectStack.pop();
+ } finally {
+ if (registry.debug()) {
+ cumulativeTime = cumulativeTime + (System.currentTimeMillis() - start);
+ System.out.println("Cumulative parse time so far : " + cumulativeTime); //$NON-NLS-1$
+ }
+ }
+ }
+
+ private void parseConfigurationElementAttributes(Attributes attributes) {
+ ConfigurationElement parentConfigurationElement = (ConfigurationElement) objectStack.peek();
+
+ // process attributes
+ int len = (attributes != null) ? attributes.getLength() : 0;
+ if (len == 0) {
+ parentConfigurationElement.setProperties(RegistryObjectManager.EMPTY_STRING_ARRAY);
+ return;
+ }
+ String[] properties = new String[len * 2];
+ for (int i = 0; i < len; i++) {
+ properties[i * 2] = attributes.getLocalName(i);
+ properties[i * 2 + 1] = translate(attributes.getValue(i));
+ }
+ parentConfigurationElement.setProperties(properties);
+ properties = null;
+ }
+
+ private void parseExtensionAttributes(Attributes attributes) {
+ Extension currentExtension = registry.getElementFactory().createExtension();
+ objectStack.push(currentExtension);
+
+ // Process Attributes
+ int len = (attributes != null) ? attributes.getLength() : 0;
+ for (int i = 0; i < len; i++) {
+ String attrName = attributes.getLocalName(i);
+ String attrValue = attributes.getValue(i).trim();
+
+ if (attrName.equals(EXTENSION_NAME))
+ currentExtension.setLabel(translate(attrValue));
+ else if (attrName.equals(EXTENSION_ID))
+ currentExtension.setSimpleIdentifier(attrValue);
+ else if (attrName.equals(EXTENSION_TARGET)) {
+ // check if point is specified as a simple or qualified name
+ String targetName;
+ if (attrValue.lastIndexOf('.') == -1) {
+ String baseId = namespace.getNamespace();
+ targetName = baseId + '.' + attrValue;
+ } else
+ targetName = attrValue;
+ currentExtension.setExtensionPointIdentifier(targetName);
+ } else
+ unknownAttribute(EXTENSION, attrName);
+ }
+ if (currentExtension.getExtensionPointIdentifier() == null) {
+ missingAttribute(EXTENSION_TARGET, EXTENSION);
+ stateStack.pop();
+ stateStack.push(new Integer(IGNORED_ELEMENT_STATE));
+ objectStack.pop();
+ return;
+ }
+
+ objectManager.add(currentExtension, true);
+ }
+
+ //todo: Are all three methods needed??
+ private void missingAttribute(String attribute, String element) {
+ if (locator == null)
+ internalError(NLS.bind(RegistryMessages.parse_missingAttribute, attribute, element));
+ else
+ internalError(NLS.bind(RegistryMessages.parse_missingAttributeLine, (new String[] {attribute, element, Integer.toString(locator.getLineNumber())})));
+ }
+
+ private void unknownAttribute(String attribute, String element) {
+ if (locator == null)
+ internalError(NLS.bind(RegistryMessages.parse_unknownAttribute, attribute, element));
+ else
+ internalError(NLS.bind(RegistryMessages.parse_unknownAttributeLine, (new String[] {attribute, element, Integer.toString(locator.getLineNumber())})));
+ }
+
+ private void unknownElement(String element, String parent) {
+ if (locator == null)
+ internalError(NLS.bind(RegistryMessages.parse_unknownElement, parent, element));
+ else
+ internalError(NLS.bind(RegistryMessages.parse_unknownElementLine, (new String[] {parent, element, Integer.toString(locator.getLineNumber())})));
+ }
+
+ private void parseExtensionPointAttributes(Attributes attributes) {
+ ExtensionPoint currentExtPoint = registry.getElementFactory().createExtensionPoint();
+
+ // Process Attributes
+ int len = (attributes != null) ? attributes.getLength() : 0;
+ for (int i = 0; i < len; i++) {
+ String attrName = attributes.getLocalName(i);
+ String attrValue = attributes.getValue(i).trim();
+
+ if (attrName.equals(EXTENSION_POINT_NAME))
+ currentExtPoint.setLabel(translate(attrValue));
+ else if (attrName.equals(EXTENSION_POINT_ID)) {
+ currentExtPoint.setUniqueIdentifier(namespace.getNamespace() + '.' + attrValue);
+ } else if (attrName.equals(EXTENSION_POINT_SCHEMA))
+ currentExtPoint.setSchema(attrValue);
+ else
+ unknownAttribute(EXTENSION_POINT, attrName);
+ }
+ if (currentExtPoint.getSimpleIdentifier() == null || currentExtPoint.getLabel() == null) {
+ String attribute = currentExtPoint.getSimpleIdentifier() == null ? EXTENSION_POINT_ID : EXTENSION_POINT_NAME;
+ missingAttribute(attribute, EXTENSION_POINT);
+ stateStack.pop();
+ stateStack.push(new Integer(IGNORED_ELEMENT_STATE));
+ return;
+ }
+
+ objectManager.addExtensionPoint(currentExtPoint, true);
+ currentExtPoint.setNamespace(namespace.getNamespace());
+ currentExtPoint.setNamespaceOwnerId(namespace.getNamespaceOwnerId());
+
+ // Now populate the the vector just below us on the objectStack with this extension point
+ scratchVectors[EXTENSION_POINT_INDEX].add(currentExtPoint);
+ }
+
+ public void startDocument() {
+ stateStack.push(new Integer(INITIAL_STATE));
+ for (int i = 0; i <= LAST_INDEX; i++) {
+ scratchVectors[i] = new ArrayList();
+ }
+ }
+
+ public void startElement(String uri, String elementName, String qName, Attributes attributes) {
+ switch (((Integer) stateStack.peek()).intValue()) {
+ case INITIAL_STATE :
+ handleInitialState(elementName, attributes);
+ break;
+ case BUNDLE_STATE :
+ handleBundleState(elementName, attributes);
+ break;
+ case BUNDLE_EXTENSION_POINT_STATE :
+ handleExtensionPointState(elementName);
+ break;
+ case BUNDLE_EXTENSION_STATE :
+ case CONFIGURATION_ELEMENT_STATE :
+ handleExtensionState(elementName, attributes);
+ break;
+ default :
+ stateStack.push(new Integer(IGNORED_ELEMENT_STATE));
+ if (!compatibilityMode)
+ internalError(NLS.bind(RegistryMessages.parse_unknownTopElement, elementName));
+ }
+ }
+
+ public void warning(SAXParseException ex) {
+ logStatus(ex);
+ }
+
+ private void internalError(String message) {
+ error(new Status(IStatus.WARNING, RegistryMessages.OWNER_NAME, PARSE_PROBLEM, message, null));
+ }
+
+ /* (non-Javadoc)
+ * @see org.xml.sax.ContentHandler#processingInstruction
+ * @since 3.0
+ */
+ public void processingInstruction(String target, String data) throws SAXException {
+ // Since 3.0, a processing instruction of the form <?eclipse version="3.0"?> at
+ // the start of the manifest file is used to indicate the plug-in manifest
+ // schema version in effect. Pre-3.0 (i.e., 2.1) plug-in manifest files do not
+ // have one of these, and this is how we can distinguish the manifest of a
+ // pre-3.0 plug-in from a post-3.0 one (for compatibility tranformations).
+ if (target.equalsIgnoreCase("eclipse")) { //$NON-NLS-1$
+ // just the presence of this processing instruction indicates that this
+ // plug-in is at least 3.0
+ schemaVersion = "3.0"; //$NON-NLS-1$
+ StringTokenizer tokenizer = new StringTokenizer(data, "=\""); //$NON-NLS-1$
+ while (tokenizer.hasMoreTokens()) {
+ String token = tokenizer.nextToken();
+ if (token.equalsIgnoreCase("version")) { //$NON-NLS-1$
+ if (!tokenizer.hasMoreTokens()) {
+ break;
+ }
+ schemaVersion = tokenizer.nextToken();
+ break;
+ }
+ }
+ }
+ }
+
+ /**
+ * Handles an error state specified by the status. The collection of all logged status
+ * objects can be accessed using <code>getStatus()</code>.
+ *
+ * @param error a status detailing the error condition
+ */
+ public void error(IStatus error) {
+ status.add(error);
+ }
+
+ protected String translate(String key) {
+ return registry.translate(key, resources);
+ }
+
+ /**
+ * Fixes up the extension declarations in the given pre-3.0 plug-in or fragment to compensate
+ * for extension points that were renamed between release 2.1 and 3.0.
+ */
+ private Extension[] fixRenamedExtensionPoints(Extension[] extensions) {
+ if (extensions == null || (schemaVersion != null && schemaVersion.equals("3.0")) || System.getProperties().get(NO_EXTENSION_MUNGING) != null) //$NON-NLS-1$
+ return extensions;
+ for (int i = 0; i < extensions.length; i++) {
+ Extension extension = extensions[i];
+ String oldPointId = extension.getExtensionPointIdentifier();
+ String newPointId = (String) extensionPointMap.get(oldPointId);
+ if (newPointId != null) {
+ extension.setExtensionPointIdentifier(newPointId);
+ }
+ }
+ return extensions;
+ }
+}
diff --git a/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/Handle.java b/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/Handle.java
new file mode 100644
index 000000000..7f8b142bb
--- /dev/null
+++ b/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/Handle.java
@@ -0,0 +1,51 @@
+/*******************************************************************************
+ * 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.registry;
+
+import org.eclipse.core.runtime.InvalidRegistryObjectException;
+
+/**
+ * A handle is the super class to all registry objects that are now served to users.
+ * The handles never hold on to any "real" content of the object being represented.
+ * A handle can become stale if its referenced object has been removed from the registry.
+ * @since 3.1.
+ */
+public abstract class Handle {
+ protected IObjectManager objectManager;
+
+ private int objectId;
+
+ protected int getId() {
+ return objectId;
+ }
+
+ Handle(IObjectManager objectManager, int value) {
+ objectId = value;
+ this.objectManager = objectManager;
+ }
+
+ /**
+ * Return the actual object corresponding to this handle.
+ * @throws InvalidRegistryObjectException when the handle is stale.
+ */
+ abstract RegistryObject getObject();
+
+ public boolean equals(Object object) {
+ if (object instanceof Handle) {
+ return objectId == ((Handle) object).objectId;
+ }
+ return false;
+ }
+
+ public int hashCode() {
+ return objectId;
+ }
+}
diff --git a/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/HashtableOfInt.java b/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/HashtableOfInt.java
new file mode 100644
index 000000000..b66810bdd
--- /dev/null
+++ b/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/HashtableOfInt.java
@@ -0,0 +1,153 @@
+/*******************************************************************************
+ * 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.registry;
+
+import java.io.*;
+
+/**
+ * Hashtable for non-zero int keys.
+ */
+
+public final class HashtableOfInt {
+ private int[] keyTable;
+ private int[] valueTable;
+ private static final float GROWTH_FACTOR = 1.33f;
+
+ private int elementSize; // number of elements in the table
+ private int threshold;
+
+ public HashtableOfInt() {
+ this(13);
+ }
+
+ public HashtableOfInt(int size) {
+ this.elementSize = 0;
+ this.threshold = size; // size represents the expected number of elements
+ int extraRoom = (int) (size * 1.33f);
+ if (this.threshold == extraRoom)
+ extraRoom++;
+ this.keyTable = new int[extraRoom];
+ this.valueTable = new int[extraRoom];
+ }
+
+ public boolean containsKey(int key) {
+ int index = key % valueTable.length;
+ int currentKey;
+ while ((currentKey = keyTable[index]) != 0) {
+ if (currentKey == key)
+ return true;
+ index = (index + 1) % keyTable.length;
+ }
+ return false;
+ }
+
+ public int get(int key) {
+ int index = key % valueTable.length;
+ int currentKey;
+ while ((currentKey = keyTable[index]) != 0) {
+ if (currentKey == key)
+ return valueTable[index];
+ index = (index + 1) % keyTable.length;
+ }
+ return Integer.MIN_VALUE;
+ }
+
+ public int removeKey(int key) {
+ int index = key % valueTable.length;
+ int currentKey;
+ while ((currentKey = keyTable[index]) != 0) {
+ if (currentKey == key) {
+ return valueTable[index];
+ }
+ index = (index + 1) % keyTable.length;
+ }
+ return Integer.MIN_VALUE;
+ }
+
+ public int put(int key, int value) {
+ int index = key % valueTable.length;
+ int currentKey;
+ while ((currentKey = keyTable[index]) != 0) {
+ if (currentKey == key)
+ return valueTable[index] = value;
+ index = (index + 1) % keyTable.length;
+ }
+ keyTable[index] = key;
+ valueTable[index] = value;
+
+ // assumes the threshold is never equal to the size of the table
+ if (++elementSize > threshold)
+ rehash();
+ return value;
+ }
+
+ private void rehash() {
+ HashtableOfInt newHashtable = new HashtableOfInt((int) (elementSize * GROWTH_FACTOR)); // double the number of expected elements
+ int currentKey;
+ for (int i = keyTable.length; --i >= 0;)
+ if ((currentKey = keyTable[i]) != 0)
+ newHashtable.put(currentKey, valueTable[i]);
+
+ this.keyTable = newHashtable.keyTable;
+ this.valueTable = newHashtable.valueTable;
+ this.threshold = newHashtable.threshold;
+ }
+
+ public int size() {
+ return elementSize;
+ }
+
+ public String toString() {
+ String s = ""; //$NON-NLS-1$
+ int object;
+ for (int i = 0, length = valueTable.length; i < length; i++)
+ if ((object = valueTable[i]) != Integer.MIN_VALUE)
+ s += keyTable[i] + " -> " + object + "\n"; //$NON-NLS-2$ //$NON-NLS-1$
+ return s;
+ }
+
+ public void save(DataOutputStream out) throws IOException {
+ out.writeInt(elementSize);
+ int tableSize = keyTable.length;
+ out.writeInt(tableSize);
+ out.writeInt(threshold);
+ for (int i = 0; i < tableSize; i++) {
+ out.writeInt(keyTable[i]);
+ out.writeInt(valueTable[i]);
+ }
+ }
+
+ public void load(DataInputStream in) throws IOException {
+ elementSize = in.readInt();
+ int tableSize = in.readInt();
+ threshold = in.readInt();
+ boolean fastMode = true;
+ if (((double) tableSize / elementSize) < GROWTH_FACTOR) {
+ keyTable = new int[(int) (elementSize * GROWTH_FACTOR)];
+ valueTable = new int[(int) (elementSize * GROWTH_FACTOR)];
+ elementSize = 0;
+ fastMode = false;
+ } else {
+ keyTable = new int[tableSize];
+ valueTable = new int[tableSize];
+ }
+ for (int i = 0; i < tableSize; i++) {
+ int key = in.readInt();
+ int value = in.readInt();
+ if (fastMode) {
+ keyTable[i] = key;
+ valueTable[i] = value;
+ } else {
+ put(key, value);
+ }
+ }
+ }
+}
diff --git a/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/HashtableOfStringAndInt.java b/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/HashtableOfStringAndInt.java
new file mode 100644
index 000000000..24e8cfc5e
--- /dev/null
+++ b/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/HashtableOfStringAndInt.java
@@ -0,0 +1,212 @@
+/*******************************************************************************
+ * 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.internal.registry;
+
+import java.io.*;
+
+/**
+ * Hashtable of {String --> int }
+ */
+public final class HashtableOfStringAndInt implements Cloneable {
+ public static final int MISSING_ELEMENT = Integer.MIN_VALUE;
+
+ // to avoid using Enumerations, walk the individual tables skipping nulls
+ private String keyTable[];
+ private int valueTable[];
+
+ private int elementSize; // number of elements in the table
+ private int threshold;
+ private static final float GROWTH_FACTOR = 1.33f;
+
+ public HashtableOfStringAndInt() {
+ this(13);
+ }
+
+ public HashtableOfStringAndInt(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.keyTable = new String[extraRoom];
+ this.valueTable = new int[extraRoom];
+ }
+
+ public Object clone() throws CloneNotSupportedException {
+ throw new CloneNotSupportedException();
+ // HashtableOfStringAndInt result = (HashtableOfStringAndInt) super.clone();
+ // result.elementSize = this.elementSize;
+ // result.threshold = this.threshold;
+ //
+ // int length = this.keyTable.length;
+ // result.keyTable = new char[length][];
+ // System.arraycopy(this.keyTable, 0, result.keyTable, 0, length);
+ //
+ // length = this.valueTable.length;
+ // result.valueTable = new Object[length];
+ // System.arraycopy(this.valueTable, 0, result.valueTable, 0, length);
+ // return result;
+ }
+
+ public boolean containsKey(String key) {
+ int index = (key.hashCode() & 0x7FFFFFFF) % valueTable.length;
+ int keyLength = key.length();
+ String currentKey;
+ while ((currentKey = keyTable[index]) != null) {
+ if (currentKey.length() == keyLength && currentKey.equals(key))
+ return true;
+ index = (index + 1) % keyTable.length;
+ }
+ return false;
+ }
+
+ public int get(String key) {
+ int index = (key.hashCode() & 0x7FFFFFFF) % valueTable.length;
+ int keyLength = key.length();
+ String currentKey;
+ while ((currentKey = keyTable[index]) != null) {
+ if (currentKey.length() == keyLength && currentKey.equals(key))
+ return valueTable[index];
+ index = (index + 1) % keyTable.length;
+ }
+ return MISSING_ELEMENT;
+ }
+
+ public int put(String key, int value) {
+ int index = (key.hashCode() & 0x7FFFFFFF) % valueTable.length;
+ int keyLength = key.length();
+ String currentKey;
+ while ((currentKey = keyTable[index]) != null) {
+ if (currentKey.length() == keyLength && currentKey.equals(key))
+ return valueTable[index] = value;
+ index = (index + 1) % keyTable.length;
+ }
+ keyTable[index] = key;
+ valueTable[index] = value;
+
+ // assumes the threshold is never equal to the size of the table
+ if (++elementSize > threshold)
+ rehash();
+ return value;
+ }
+
+ public int removeKey(String key) {
+ int index = (key.hashCode() & 0x7FFFFFFF) % valueTable.length;
+ int keyLength = key.length();
+ String currentKey;
+ while ((currentKey = keyTable[index]) != null) {
+ if (currentKey.length() == keyLength && currentKey.equals(key)) {
+ int value = valueTable[index];
+ elementSize--;
+ keyTable[index] = null;
+ valueTable[index] = MISSING_ELEMENT;
+ rehash();
+ return value;
+ }
+ index = (index + 1) % keyTable.length;
+ }
+ return MISSING_ELEMENT;
+ }
+
+ private void rehash() {
+ HashtableOfStringAndInt newHashtable = new HashtableOfStringAndInt((int) (elementSize * GROWTH_FACTOR));
+ String currentKey;
+ for (int i = keyTable.length; --i >= 0;)
+ if ((currentKey = keyTable[i]) != null)
+ newHashtable.put(currentKey, valueTable[i]);
+
+ this.keyTable = newHashtable.keyTable;
+ this.valueTable = newHashtable.valueTable;
+ this.threshold = newHashtable.threshold;
+ }
+
+ public int size() {
+ return elementSize;
+ }
+
+ public String toString() {
+ String s = ""; //$NON-NLS-1$
+ int object;
+ for (int i = 0, length = valueTable.length; i < length; i++)
+ if ((object = valueTable[i]) != MISSING_ELEMENT)
+ s += new String(keyTable[i]) + " -> " + object + "\n"; //$NON-NLS-2$ //$NON-NLS-1$
+ return s;
+ }
+
+ public int[] getValues() {
+ int keyTableLength = keyTable.length;
+ int[] result = new int[size()];
+ int j = 0;
+ for (int i = 0; i < keyTableLength; i++) {
+ if (keyTable[i] != null)
+ result[j++] = valueTable[i];
+ }
+ return result;
+ }
+
+ public void save(DataOutputStream out) throws IOException {
+ out.writeInt(elementSize);
+ int tableSize = keyTable.length;
+ out.writeInt(tableSize);
+ out.writeInt(threshold);
+ for (int i = 0; i < tableSize; i++) {
+ writeStringOrNull(keyTable[i], out);
+ out.writeInt(valueTable[i]);
+ }
+ }
+
+ public void load(DataInputStream in) throws IOException {
+ elementSize = in.readInt();
+ int tableSize = in.readInt();
+ threshold = in.readInt();
+ boolean fastMode = true;
+ if (((double) tableSize / elementSize) < GROWTH_FACTOR) {
+ keyTable = new String[(int) (elementSize * GROWTH_FACTOR)];
+ valueTable = new int[(int) (elementSize * GROWTH_FACTOR)];
+ elementSize = 0;
+ fastMode = false;
+ } else {
+ keyTable = new String[tableSize];
+ valueTable = new int[tableSize];
+ }
+ for (int i = 0; i < tableSize; i++) {
+ String key = readStringOrNull(in);
+ int value = in.readInt();
+ if (fastMode) {
+ keyTable[i] = key;
+ valueTable[i] = value;
+ } else {
+ if (key != null)
+ put(key, value);
+ }
+ }
+ }
+
+ private static final byte NULL = 0;
+ private static final byte OBJECT = 1;
+
+ private void writeStringOrNull(String string, DataOutputStream out) throws IOException {
+ if (string == null)
+ out.writeByte(NULL);
+ else {
+ out.writeByte(OBJECT);
+ out.writeUTF(string);
+ }
+ }
+
+ private String readStringOrNull(DataInputStream in) throws IOException {
+ byte type = in.readByte();
+ if (type == NULL)
+ return null;
+ return in.readUTF();
+ }
+
+}
diff --git a/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/ICompatibilityStrategy.java b/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/ICompatibilityStrategy.java
new file mode 100644
index 000000000..2f8e868c8
--- /dev/null
+++ b/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/ICompatibilityStrategy.java
@@ -0,0 +1,59 @@
+/*******************************************************************************
+ * 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.registry;
+
+import java.util.EventListener;
+import java.util.Map;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.equinox.registry.IConfigurationElement;
+
+/**
+ * This interfaces provides a bridge between the "old" org.eclipse.core.runitime elements
+ * and "new" org.eclipse.equinox.registry elements. While registry internally operates
+ * with "new" elements, listeners might expect "old" element types.
+ *
+ * The interface provides backward compatibility. It is not expected to be implemented
+ * or extended by clients.
+ *
+ * @since org.eclipse.equinox.registry 1.0
+ */
+public interface ICompatibilityStrategy {
+ /**
+ * Implement to ADD an additional listener invocation mechanism.
+ * (Default meachnism is executed first, this implementation is executed second.)
+ *
+ * @param listener - the listener to be invoked
+ * @param deltas - event deltas
+ * @param filter - the listener filter string
+ */
+ public void invokeListener(EventListener listener, Map deltas, String filter);
+
+ /**
+ * Implement to ADD an additional initialization path for the CreateExecutableExtensions.
+ * (Default meachnism is executed first, this implementation is executed second.)
+ * @param newClassInstance - class just created for an executable extension
+ * @param confElement - ConfigurationElement that triggered creation of this element
+ * @param propertyName - name of the attribute describing class to be created
+ * @param initData - initialization data
+ * @throws CoreException
+ */
+ public void setInitializationData(Object newClassInstance, IConfigurationElement confElement, String propertyName, Object initData) throws CoreException;
+
+
+ /**
+ * Implement to ADD an additional creation path for the CreateExecutableExtensions.
+ * (Default meachnism is executed first, this implementation is executed second.)
+ *
+ * @param result - object being created
+ * @return object returned by the IExecutableExtensionFactory.create() method
+ */
+ public Object create(Object result) throws CoreException ;
+}
diff --git a/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/IObjectManager.java b/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/IObjectManager.java
new file mode 100644
index 000000000..c75e7aaf2
--- /dev/null
+++ b/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/IObjectManager.java
@@ -0,0 +1,26 @@
+/*******************************************************************************
+ * 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.registry;
+
+/**
+ * @since 3.1
+ */
+public interface IObjectManager {
+ Handle getHandle(int id, byte type);
+
+ Handle[] getHandles(int[] ids, byte type);
+
+ Object getObject(int id, byte type);
+
+ RegistryObject[] getObjects(int[] values, byte type);
+
+ void close();
+}
diff --git a/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/IRegistryConstants.java b/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/IRegistryConstants.java
new file mode 100644
index 000000000..ce605b09d
--- /dev/null
+++ b/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/IRegistryConstants.java
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * 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.registry;
+
+public interface IRegistryConstants {
+
+ /**
+ * The unique identifier constant (value "<code>org.eclipse.core.runtime</code>")
+ * of the Core Runtime (pseudo-) plug-in.
+ */
+ public static final String RUNTIME_NAME = "org.eclipse.core.runtime"; //$NON-NLS-1$
+
+ // command line options
+ public static final String NO_REGISTRY_CACHE = "-noregistrycache"; //$NON-NLS-1$
+ public static final String NO_LAZY_REGISTRY_CACHE_LOADING = "-noLazyRegistryCacheLoading"; //$NON-NLS-1$
+
+ // System options
+ public static final String PROP_NO_LAZY_CACHE_LOADING = "eclipse.noLazyRegistryCacheLoading"; //$NON-NLS-1$
+ public static final String PROP_CHECK_CONFIG = "osgi.checkConfiguration"; //$NON-NLS-1$
+ public static final String PROP_NO_REGISTRY_CACHE = "eclipse.noRegistryCache"; //$NON-NLS-1$
+
+ // 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$
+
+ /**
+ * Specific error code supplied to the Status objects
+ */
+ static final int PLUGIN_ERROR = 1;
+}
diff --git a/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/KeyedElement.java b/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/KeyedElement.java
new file mode 100644
index 000000000..f58774044
--- /dev/null
+++ b/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/KeyedElement.java
@@ -0,0 +1,19 @@
+/*******************************************************************************
+ * 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.internal.registry;
+
+public interface KeyedElement {
+ public int getKeyHashCode();
+
+ public boolean compare(KeyedElement other);
+
+ public Object getKey();
+}
diff --git a/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/KeyedHashSet.java b/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/KeyedHashSet.java
new file mode 100644
index 000000000..0e0c57988
--- /dev/null
+++ b/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/KeyedHashSet.java
@@ -0,0 +1,316 @@
+/*******************************************************************************
+ * 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.registry;
+
+/**
+ * Note this is a copy of a data structure from OSGi, package
+ * org.eclipse.osgi.framework.internal.core. Unused methods were removed
+ * from this copy.
+ */
+public class KeyedHashSet {
+ protected static final int MINIMUM_SIZE = 7;
+ private int capacity;
+ protected int elementCount = 0;
+ protected KeyedElement[] elements;
+ protected boolean replace;
+
+ public KeyedHashSet() {
+ this(MINIMUM_SIZE, true);
+ }
+
+ public KeyedHashSet(int capacity) {
+ this(capacity, true);
+ }
+
+ public KeyedHashSet(int capacity, boolean replace) {
+ elements = new KeyedElement[Math.max(MINIMUM_SIZE, capacity * 2)];
+ this.replace = replace;
+ this.capacity = capacity;
+ }
+
+ /**
+ * Adds an element to this set. If an element with the same key already exists,
+ * replaces it depending on the replace flag.
+ * @return true if the element was added/stored, false otherwise
+ */
+ public boolean add(KeyedElement element) {
+ int hash = hash(element);
+
+ // search for an empty slot at the end of the array
+ for (int i = hash; i < elements.length; i++) {
+ if (elements[i] == null) {
+ elements[i] = element;
+ elementCount++;
+ // grow if necessary
+ if (shouldGrow())
+ expand();
+ return true;
+ }
+ if (elements[i].compare(element)) {
+ if (replace)
+ elements[i] = element;
+ return replace;
+ }
+ }
+
+ // search for an empty slot at the beginning of the array
+ for (int i = 0; i < hash - 1; i++) {
+ if (elements[i] == null) {
+ elements[i] = element;
+ elementCount++;
+ // grow if necessary
+ if (shouldGrow())
+ expand();
+ return true;
+ }
+ if (elements[i].compare(element)) {
+ if (replace)
+ elements[i] = element;
+ return replace;
+ }
+ }
+
+ // if we didn't find a free slot, then try again with the expanded set
+ expand();
+ return add(element);
+ }
+
+ public void clear() {
+ elements = new KeyedElement[Math.max(MINIMUM_SIZE, capacity * 2)];
+ elementCount = 0;
+ }
+
+ public KeyedElement[] elements() {
+ return (KeyedElement[]) elements(new KeyedElement[elementCount]);
+ }
+
+ public Object[] elements(Object[] result) {
+ int j = 0;
+ for (int i = 0; i < elements.length; i++) {
+ KeyedElement element = elements[i];
+ if (element != null)
+ result[j++] = element;
+ }
+ return result;
+ }
+
+ /**
+ * The array isn't large enough so double its size and rehash
+ * all its current values.
+ */
+ protected void expand() {
+ KeyedElement[] oldElements = elements;
+ elements = new KeyedElement[elements.length * 2];
+
+ int maxArrayIndex = elements.length - 1;
+ for (int i = 0; i < oldElements.length; i++) {
+ KeyedElement element = oldElements[i];
+ if (element != null) {
+ int hash = hash(element);
+ while (elements[hash] != null) {
+ hash++;
+ if (hash > maxArrayIndex)
+ hash = 0;
+ }
+ elements[hash] = element;
+ }
+ }
+ }
+
+ /**
+ * Returns the set element with the given id, or null
+ * if not found.
+ */
+ public KeyedElement get(KeyedElement key) {
+ if (elementCount == 0)
+ return null;
+ int hash = hash(key);
+
+ // search the last half of the array
+ for (int i = hash; i < elements.length; i++) {
+ KeyedElement element = elements[i];
+ if (element == null)
+ return null;
+ if (element.compare(key))
+ return element;
+ }
+
+ // search the beginning of the array
+ for (int i = 0; i < hash - 1; i++) {
+ KeyedElement element = elements[i];
+ if (element == null)
+ return null;
+ if (element.compare(key))
+ return element;
+ }
+
+ // nothing found so return null
+ return null;
+ }
+
+ /**
+ * Returns the set element with the given id, or null
+ * if not found.
+ */
+ public KeyedElement getByKey(Object key) {
+ if (elementCount == 0)
+ return null;
+ int hash = keyHash(key);
+
+ // search the last half of the array
+ for (int i = hash; i < elements.length; i++) {
+ KeyedElement element = elements[i];
+ if (element == null)
+ return null;
+ if (element.getKey().equals(key))
+ return element;
+ }
+
+ // search the beginning of the array
+ for (int i = 0; i < hash - 1; i++) {
+ KeyedElement element = elements[i];
+ if (element == null)
+ return null;
+ if (element.getKey().equals(key))
+ return element;
+ }
+
+ // nothing found so return null
+ return null;
+ }
+
+ private int hash(KeyedElement element) {
+ return Math.abs(element.getKeyHashCode()) % elements.length;
+ }
+
+ public boolean isEmpty() {
+ return elementCount == 0;
+ }
+
+ private int keyHash(Object key) {
+ return Math.abs(key.hashCode()) % elements.length;
+ }
+
+ /**
+ * The element at the given index has been removed so move
+ * elements to keep the set properly hashed.
+ */
+ protected void rehashTo(int anIndex) {
+
+ int target = anIndex;
+ int index = anIndex + 1;
+ if (index >= elements.length)
+ index = 0;
+ KeyedElement element = elements[index];
+ while (element != null) {
+ int hashIndex = hash(element);
+ boolean match;
+ if (index < target)
+ match = !(hashIndex > target || hashIndex <= index);
+ else
+ match = !(hashIndex > target && hashIndex <= index);
+ if (match) {
+ elements[target] = element;
+ target = index;
+ }
+ index++;
+ if (index >= elements.length)
+ index = 0;
+ element = elements[index];
+ }
+ elements[target] = null;
+ }
+
+ public boolean remove(KeyedElement toRemove) {
+ if (elementCount == 0)
+ return false;
+
+ int hash = hash(toRemove);
+
+ for (int i = hash; i < elements.length; i++) {
+ KeyedElement element = elements[i];
+ if (element == null)
+ return false;
+ if (element.compare(toRemove)) {
+ rehashTo(i);
+ elementCount--;
+ return true;
+ }
+ }
+
+ for (int i = 0; i < hash - 1; i++) {
+ KeyedElement element = elements[i];
+ if (element == null)
+ return false;
+ if (element.compare(toRemove)) {
+ rehashTo(i);
+ elementCount--;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public boolean removeByKey(Object key) {
+ if (elementCount == 0)
+ return false;
+ int hash = keyHash(key);
+
+ for (int i = hash; i < elements.length; i++) {
+ KeyedElement element = elements[i];
+ if (element == null)
+ return false;
+ if (element.getKey().equals(key)) {
+ rehashTo(i);
+ elementCount--;
+ return true;
+ }
+ }
+
+ for (int i = 0; i < hash - 1; i++) {
+ KeyedElement element = elements[i];
+ if (element == null)
+ return false;
+ if (element.getKey().equals(key)) {
+ rehashTo(i);
+ elementCount--;
+ return true;
+ }
+ }
+
+ return true;
+ }
+
+ private boolean shouldGrow() {
+ return elementCount > elements.length * 0.75;
+ }
+
+ public int size() {
+ return elementCount;
+ }
+
+ public String toString() {
+ StringBuffer result = new StringBuffer(100);
+ result.append("{"); //$NON-NLS-1$
+ boolean first = true;
+ for (int i = 0; i < elements.length; i++) {
+ if (elements[i] != null) {
+ if (first)
+ first = false;
+ else
+ result.append(", "); //$NON-NLS-1$
+ result.append(elements[i]);
+ }
+ }
+ result.append("}"); //$NON-NLS-1$
+ return result.toString();
+ }
+} \ No newline at end of file
diff --git a/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/ReadWriteMonitor.java b/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/ReadWriteMonitor.java
new file mode 100644
index 000000000..52e4e4943
--- /dev/null
+++ b/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/ReadWriteMonitor.java
@@ -0,0 +1,105 @@
+/*******************************************************************************
+ * 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.registry;
+
+/**
+ * Monitor ensuring no more than one writer working concurrently.
+ * Multiple readers are allowed to perform simultaneously.
+ *
+ * This class was borrowed from org.eclipse.jdt.internal.core.search.indexing.
+ */
+public class ReadWriteMonitor {
+
+ /**
+ * <0 : writing (cannot go beyond -1, i.e one concurrent writer)
+ * =0 : idle
+ * >0 : reading (number of concurrent readers)
+ */
+ private int status = 0;
+
+ private Thread writeLockowner;
+
+ /**
+ * Concurrent reading is allowed
+ * Blocking only when already writing.
+ */
+ public synchronized void enterRead() {
+ if (writeLockowner == Thread.currentThread())
+ return;
+ while (status < 0) {
+ try {
+ wait();
+ } catch (InterruptedException e) {
+ // ignore
+ }
+ }
+ status++;
+ }
+
+ /**
+ * Only one writer at a time is allowed to perform
+ * Blocking only when already writing or reading.
+ */
+ public synchronized void enterWrite() {
+ if (writeLockowner != Thread.currentThread()) {
+ while (status != 0) {
+ try {
+ wait();
+ } catch (InterruptedException e) {
+ // ignore
+ }
+ }
+// System.out.println(this + "lockowner:" + Thread.currentThread());
+ writeLockowner = Thread.currentThread();
+ }
+ status--;
+ }
+
+ /**
+ * Only notify waiting writer(s) if last reader
+ */
+ public synchronized void exitRead() {
+ if (writeLockowner == Thread.currentThread())
+ return;
+ if (--status == 0)
+ notifyAll();
+ }
+
+ /**
+ * When writing is over, all readers and possible
+ * writers are granted permission to restart concurrently
+ */
+ public synchronized void exitWrite() {
+ if (writeLockowner != Thread.currentThread())
+ throw new IllegalStateException("Current owner is " + writeLockowner); //$NON-NLS-1$
+ if (++status == 0) {
+ // System.out.println(this + "exitWrite:" + Thread.currentThread());
+ writeLockowner = null;
+ notifyAll();
+ }
+ }
+
+ public String toString() {
+ StringBuffer buffer = new StringBuffer();
+ buffer.append(this.hashCode());
+ if (status == 0) {
+ buffer.append("Monitor idle "); //$NON-NLS-1$
+ } else if (status < 0) {
+ buffer.append("Monitor writing "); //$NON-NLS-1$
+ } else if (status > 0) {
+ buffer.append("Monitor reading "); //$NON-NLS-1$
+ }
+ buffer.append("(status = "); //$NON-NLS-1$
+ buffer.append(this.status);
+ buffer.append(")"); //$NON-NLS-1$
+ return buffer.toString();
+ }
+}
diff --git a/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/ReferenceMap.java b/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/ReferenceMap.java
new file mode 100644
index 000000000..2da387c2e
--- /dev/null
+++ b/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/ReferenceMap.java
@@ -0,0 +1,407 @@
+/**
+ * Copyright 2001-2004 The Apache Software Foundation
+ * Portions (modifications) Copyright 2004-2005 IBM Corp.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * Contributors:
+ * Apache Software Foundation - Initial implementation
+ * Pascal Rapicault, IBM - Pascal remove the entrySet() implementation because it relied on another class.
+ * IBM - change to int keys, remove support for weak references, and remove unused methods
+ */
+package org.eclipse.core.internal.registry;
+
+import java.lang.ref.*;
+
+/**
+ * Hashtable-based map with integer keys that allows values to be removed
+ * by the garbage collector.<P>
+ *
+ * When you construct a <Code>ReferenceMap</Code>, you can
+ * specify what kind of references are used to store the
+ * map's values. If non-hard references are
+ * used, then the garbage collector can remove mappings
+ * if a value becomes unreachable, or if the
+ * JVM's memory is running low. For information on how
+ * the different reference types behave, see
+ * {@link Reference}.<P>
+ *
+ * The algorithms used are basically the same as those
+ * in {@link java.util.HashMap}. In particular, you
+ * can specify a load factor and capacity to suit your
+ * needs.
+ *
+ * This map does <I>not</I> allow null values. Attempting to add a null
+ * value to the map will raise a <Code>NullPointerException</Code>.<P>
+ *
+ * This data structure is not synchronized.
+ *
+ * @see java.lang.ref.Reference
+ */
+public class ReferenceMap {
+
+ /**
+ * IEntry implementation that acts as a hard reference.
+ * The value of a hard reference entry is never garbage
+ * collected until it is explicitly removed from the map.
+ */
+ private static class HardRef implements IEntry {
+
+ private int key;
+ private IEntry next;
+ /**
+ * Reference value. Note this can never be null.
+ */
+ private Object value;
+
+ public HardRef(int key, Object value, IEntry next) {
+ this.key = key;
+ this.value = value;
+ this.next = next;
+ }
+
+ public int getKey() {
+ return key;
+ }
+
+ public IEntry getNext() {
+ return next;
+ }
+
+ public Object getValue() {
+ return value;
+ }
+
+ public void setNext(IEntry next) {
+ this.next = next;
+ }
+
+ public String toString() {
+ return "HardRef(" + key + ',' + value + ')'; //$NON-NLS-1$
+ }
+ }
+
+ /**
+ * The common interface for all elements in the map. Both
+ * hard and soft map values conform to this interface.
+ */
+ private static interface IEntry {
+ /**
+ * Returns the integer key for this entry.
+ * @return The integer key
+ */
+ public int getKey();
+
+ /**
+ * Returns the next entry in the linked list of entries
+ * with the same hash value, or <code>null</code>
+ * if there is no next entry.
+ * @return The next entry, or <code>null</code>.
+ */
+ public IEntry getNext();
+
+ /**
+ * Returns the value of this entry.
+ * @return The entry value.
+ */
+ public Object getValue();
+
+ /**
+ * Sets the next entry in the linked list of map entries
+ * with the same hash value.
+ *
+ * @param next The next entry, or <code>null</code>.
+ */
+ public void setNext(IEntry next);
+ }
+
+ /**
+ * Augments a normal soft reference with additional information
+ * required to implement the IEntry interface.
+ */
+ private static class SoftRef extends SoftReference implements IEntry {
+ private int key;
+ /**
+ * For chained collisions
+ */
+ private IEntry next;
+
+ public SoftRef(int key, Object value, IEntry next, ReferenceQueue q) {
+ super(value, q);
+ this.key = key;
+ this.next = next;
+ }
+
+ public int getKey() {
+ return key;
+ }
+
+ public IEntry getNext() {
+ return next;
+ }
+
+ public Object getValue() {
+ return super.get();
+ }
+
+ public void setNext(IEntry next) {
+ this.next = next;
+ }
+ }
+
+ /**
+ * 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;
+
+ private int entryCount;
+
+ /**
+ * The threshold variable is calculated by multiplying
+ * table.length and loadFactor.
+ * Note: I originally marked this field as final, but then this class
+ * didn't compile under JDK1.2.2.
+ * @serial
+ */
+ private float loadFactor;
+
+ /**
+ * ReferenceQueue used to eliminate stale mappings.
+ */
+ private transient ReferenceQueue queue = new ReferenceQueue();
+
+ /**
+ * Number of mappings in this map.
+ */
+ private transient int size;
+
+ /**
+ * The hash table. Its length is always a power of two.
+ */
+ private transient IEntry[] table;
+
+ /**
+ * When size reaches threshold, the map is resized.
+ * @see #resize()
+ */
+ private transient int threshold;
+
+ /**
+ * The reference type for values. Must be HARD or SOFT
+ * Note: I originally marked this field as final, but then this class
+ * didn't compile under JDK1.2.2.
+ * @serial
+ */
+ int valueType;
+
+ /**
+ * Constructs a new <Code>ReferenceMap</Code> with the
+ * specified reference type, load factor and initial
+ * capacity.
+ *
+ * @param referenceType the type of reference to use for values;
+ * must be {@link #HARD} or {@link #SOFT}
+ * @param capacity the initial capacity for the map
+ * @param loadFactor the load factor for the map
+ */
+ public ReferenceMap(int referenceType, int capacity, float loadFactor) {
+ super();
+ if (referenceType != HARD && referenceType != SOFT)
+ throw new IllegalArgumentException(" must be HARD or SOFT."); //$NON-NLS-1$
+ if (capacity <= 0)
+ throw new IllegalArgumentException("capacity must be positive"); //$NON-NLS-1$
+ if ((loadFactor <= 0.0f) || (loadFactor >= 1.0f))
+ throw new IllegalArgumentException("Load factor must be greater than 0 and less than 1."); //$NON-NLS-1$
+
+ this.valueType = referenceType;
+
+ int initialSize = 1;
+ while (initialSize < capacity)
+ initialSize *= 2;
+
+ this.table = new IEntry[initialSize];
+ this.loadFactor = loadFactor;
+ this.threshold = (int) (initialSize * loadFactor);
+ }
+
+ /**
+ * @param key
+ * @return
+ */
+ private Object doRemove(int key) {
+ int index = indexFor(key);
+ IEntry previous = null;
+ IEntry entry = table[index];
+ while (entry != null) {
+ if (key == entry.getKey()) {
+ if (previous == null)
+ table[index] = entry.getNext();
+ else
+ previous.setNext(entry.getNext());
+ this.size--;
+ return entry.getValue();
+ }
+ previous = entry;
+ entry = entry.getNext();
+ }
+ return null;
+ }
+
+ /**
+ * Returns the value associated with the given key, if any.
+ *
+ * @return the value associated with the given key, or <Code>null</Code>
+ * if the key maps to no value
+ */
+ public Object get(int key) {
+ purge();
+ for (IEntry entry = table[indexFor(key)]; entry != null; entry = entry.getNext())
+ if (entry.getKey() == key)
+ return entry.getValue();
+ return null;
+ }
+
+ /**
+ * Converts the given hash code into an index into the
+ * hash table.
+ */
+ private int indexFor(int hash) {
+ // mix the bits to avoid bucket collisions...
+ hash += ~(hash << 15);
+ hash ^= (hash >>> 10);
+ hash += (hash << 3);
+ hash ^= (hash >>> 6);
+ hash += ~(hash << 11);
+ hash ^= (hash >>> 16);
+ return hash & (table.length - 1);
+ }
+
+ /**
+ * Constructs a new table entry for the given data
+ *
+ * @param key The entry key
+ * @param value The entry value
+ * @param next The next value in the entry's collision chain
+ * @return The new table entry
+ */
+ private IEntry newEntry(int key, Object value, IEntry next) {
+ entryCount++;
+ switch (valueType) {
+ case HARD :
+ return new HardRef(key, value, next);
+ case SOFT :
+ return new SoftRef(key, value, next, queue);
+ default :
+ throw new Error();
+ }
+ }
+
+ /**
+ * Purges stale mappings from this map.<P>
+ *
+ * Ordinarily, stale mappings are only removed during
+ * a write operation; typically a write operation will
+ * occur often enough that you'll never need to manually
+ * invoke this method.<P>
+ *
+ * Note that this method is not synchronized! Special
+ * care must be taken if, for instance, you want stale
+ * mappings to be removed on a periodic basis by some
+ * background thread.
+ */
+ private void purge() {
+ Reference ref = queue.poll();
+ while (ref != null) {
+ doRemove(((IEntry) ref).getKey());
+ ref.clear();
+ ref = queue.poll();
+ }
+ }
+
+ /**
+ * Associates the given key with the given value.<P>
+ * Neither the key nor the value may be null.
+ *
+ * @param key the key of the mapping
+ * @param value the value of the mapping
+ * @throws NullPointerException if either the key or value
+ * is null
+ */
+ public void put(int key, Object value) {
+ if (value == null)
+ throw new NullPointerException("null values not allowed"); //$NON-NLS-1$
+
+ purge();
+
+ if (size + 1 > threshold)
+ resize();
+
+ int index = indexFor(key);
+ IEntry previous = null;
+ IEntry entry = table[index];
+ while (entry != null) {
+ if (key == entry.getKey()) {
+ if (previous == null)
+ table[index] = newEntry(key, value, entry.getNext());
+ else
+ previous.setNext(newEntry(key, value, entry.getNext()));
+ return;
+ }
+ previous = entry;
+ entry = entry.getNext();
+ }
+ this.size++;
+ table[index] = newEntry(key, value, table[index]);
+ }
+
+ /**
+ * Removes the key and its associated value from this map.
+ *
+ * @param key the key to remove
+ * @return the value associated with that key, or null if
+ * the key was not in the map
+ */
+ public Object remove(int key) {
+ purge();
+ return doRemove(key);
+ }
+
+ /**
+ * Resizes this hash table by doubling its capacity.
+ * This is an expensive operation, as entries must
+ * be copied from the old smaller table to the new
+ * bigger table.
+ */
+ private void resize() {
+ IEntry[] old = table;
+ table = new IEntry[old.length * 2];
+
+ for (int i = 0; i < old.length; i++) {
+ IEntry next = old[i];
+ while (next != null) {
+ IEntry entry = next;
+ next = next.getNext();
+ int index = indexFor(entry.getKey());
+ entry.setNext(table[index]);
+ table[index] = entry;
+ }
+ old[i] = null;
+ }
+ threshold = (int) (table.length * loadFactor);
+ }
+} \ No newline at end of file
diff --git a/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/RegistryChangeEvent.java b/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/RegistryChangeEvent.java
new file mode 100644
index 000000000..e35df396b
--- /dev/null
+++ b/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/RegistryChangeEvent.java
@@ -0,0 +1,87 @@
+/*******************************************************************************
+ * 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.internal.registry;
+
+import java.util.Arrays;
+import java.util.Map;
+import org.eclipse.equinox.registry.IExtensionDelta;
+import org.eclipse.equinox.registry.IRegistryChangeEvent;
+
+/**
+ * A registry change event implementation. A filter can be specified. In that case, only
+ * deltas for the selected host will be available to clients.
+ */
+public final class RegistryChangeEvent implements IRegistryChangeEvent {
+ private String filter;
+ private Map deltas;
+
+ public RegistryChangeEvent(Map deltas, String filter) {
+ this.deltas = deltas;
+ this.filter = filter;
+ }
+
+ private RegistryDelta[] getHostDeltas() {
+ // if there is a filter, return only the delta for the selected plug-in
+ if (filter != null) {
+ RegistryDelta singleDelta = getHostDelta(filter);
+ return singleDelta == null ? new RegistryDelta[0] : new RegistryDelta[] {singleDelta};
+ }
+ // there is no filter - return all deltas
+ return (RegistryDelta[]) deltas.values().toArray(new RegistryDelta[deltas.size()]);
+ }
+
+ private RegistryDelta getHostDelta(String pluginId) {
+ if (filter != null && !pluginId.equals(filter))
+ return null;
+ return (RegistryDelta) deltas.get(pluginId);
+ }
+
+ public IExtensionDelta[] getExtensionDeltas() {
+ RegistryDelta[] hostDeltas = getHostDeltas();
+ if (hostDeltas.length == 0)
+ return new IExtensionDelta[0];
+ int extensionDeltasSize = 0;
+ for (int i = 0; i < hostDeltas.length; i++)
+ extensionDeltasSize += hostDeltas[i].getExtensionDeltasCount();
+ IExtensionDelta[] extensionDeltas = new IExtensionDelta[extensionDeltasSize];
+ for (int i = 0, offset = 0; i < hostDeltas.length; i++) {
+ IExtensionDelta[] hostExtDeltas = hostDeltas[i].getExtensionDeltas();
+ System.arraycopy(hostExtDeltas, 0, extensionDeltas, offset, hostExtDeltas.length);
+ offset += hostExtDeltas.length;
+ }
+ return extensionDeltas;
+ }
+
+ public IExtensionDelta[] getExtensionDeltas(String hostName) {
+ RegistryDelta hostDelta = getHostDelta(hostName);
+ if (hostDelta == null)
+ return new IExtensionDelta[0];
+ return hostDelta.getExtensionDeltas();
+ }
+
+ public IExtensionDelta[] getExtensionDeltas(String hostName, String extensionPoint) {
+ RegistryDelta hostDelta = getHostDelta(hostName);
+ if (hostDelta == null)
+ return new IExtensionDelta[0];
+ return hostDelta.getExtensionDeltas(hostName + '.' + extensionPoint);
+ }
+
+ public IExtensionDelta getExtensionDelta(String hostName, String extensionPoint, String extension) {
+ RegistryDelta hostDelta = getHostDelta(hostName);
+ if (hostDelta == null)
+ return null;
+ return hostDelta.getExtensionDelta(hostName + '.' + extensionPoint, extension);
+ }
+
+ public String toString() {
+ return "RegistryChangeEvent: " + Arrays.asList(getHostDeltas()); //$NON-NLS-1$
+ }
+}
diff --git a/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/RegistryDelta.java b/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/RegistryDelta.java
new file mode 100644
index 000000000..d25cb73fd
--- /dev/null
+++ b/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/RegistryDelta.java
@@ -0,0 +1,77 @@
+/*******************************************************************************
+ * 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.internal.registry;
+
+import java.util.*;
+import org.eclipse.equinox.registry.IExtension;
+import org.eclipse.equinox.registry.IExtensionDelta;
+
+/**
+ * The extension deltas are grouped by namespace. There is one registry delta by namespace.
+ */
+public class RegistryDelta {
+ private Set extensionDeltas = new HashSet(); //the extension deltas (each element indicate the type of the delta)
+ private IObjectManager objectManager; //The object manager from which all the objects contained in the deltas will be found.
+
+ RegistryDelta() {
+ //Nothing to do
+ }
+
+ public int getExtensionDeltasCount() {
+ return extensionDeltas.size();
+ }
+
+ public IExtensionDelta[] getExtensionDeltas() {
+ return (IExtensionDelta[]) extensionDeltas.toArray(new ExtensionDelta[extensionDeltas.size()]);
+ }
+
+ public IExtensionDelta[] getExtensionDeltas(String extensionPoint) {
+ Collection selectedExtDeltas = new LinkedList();
+ for (Iterator extDeltasIter = extensionDeltas.iterator(); extDeltasIter.hasNext();) {
+ IExtensionDelta extensionDelta = (IExtensionDelta) extDeltasIter.next();
+ if (extensionDelta.getExtension().getExtensionPointUniqueIdentifier().equals(extensionPoint))
+ selectedExtDeltas.add(extensionDelta);
+ }
+ return (IExtensionDelta[]) selectedExtDeltas.toArray(new IExtensionDelta[selectedExtDeltas.size()]);
+ }
+
+ /**
+ * @param extensionPointId
+ * @param extensionId must not be null
+ */
+ public IExtensionDelta getExtensionDelta(String extensionPointId, String extensionId) {
+ for (Iterator extDeltasIter = extensionDeltas.iterator(); extDeltasIter.hasNext();) {
+ IExtensionDelta extensionDelta = (IExtensionDelta) extDeltasIter.next();
+ IExtension extension = extensionDelta.getExtension();
+ if (extension.getExtensionPointUniqueIdentifier().equals(extensionPointId) && extension.getUniqueIdentifier() != null && extension.getUniqueIdentifier().equals(extensionId))
+ return extensionDelta;
+ }
+ return null;
+ }
+
+ void addExtensionDelta(IExtensionDelta extensionDelta) {
+ this.extensionDeltas.add(extensionDelta);
+ ((ExtensionDelta) extensionDelta).setContainingDelta(this);
+ }
+
+ public String toString() {
+ return "\n\tHost " + ": " + extensionDeltas; //$NON-NLS-1$//$NON-NLS-2$
+ }
+
+ void setObjectManager(IObjectManager objectManager) {
+ this.objectManager = objectManager;
+ //TODO May want to add into the existing one here... if it is possible to have batching
+ }
+
+ public IObjectManager getObjectManager() {
+ return objectManager;
+ }
+}
diff --git a/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/RegistryMessages.java b/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/RegistryMessages.java
new file mode 100644
index 000000000..66e9f3e5b
--- /dev/null
+++ b/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/RegistryMessages.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 - Initial API and implementation
+ **********************************************************************/
+package org.eclipse.core.internal.registry;
+
+import org.eclipse.osgi.util.NLS;
+
+// Runtime plugin message catalog
+public class RegistryMessages extends NLS {
+ /**
+ * The unique identifier constant of this plug-in.
+ */
+ public static final String OWNER_NAME = "org.eclipse.equinox.registry"; //$NON-NLS-1$
+
+ private static final String BUNDLE_NAME = "org.eclipse.core.internal.registry.messages"; //$NON-NLS-1$
+
+ // Extension Registry
+ public static String meta_registryCacheWriteProblems;
+ public static String meta_registryCacheReadProblems;
+ public static String meta_regCacheIOExceptionReading;
+ public static String meta_registryCacheInconsistent;
+ public static String meta_unableToCreateCache;
+ public static String meta_unableToReadCache;
+ public static String registry_no_default;
+
+ // parsing/resolve
+ public static String parse_error;
+ public static String parse_errorNameLineColumn;
+ public static String parse_internalStack;
+ public static String parse_missingAttribute;
+ public static String parse_missingAttributeLine;
+ public static String parse_unknownAttribute;
+ public static String parse_unknownAttributeLine;
+ public static String parse_unknownElement;
+ public static String parse_unknownElementLine;
+ public static String parse_unknownTopElement;
+ public static String parse_xmlParserNotAvailable;
+ public static String parse_process;
+ public static String parse_failedParsingManifest;
+ public static String parse_nonSingleton;
+ public static String parse_problems;
+
+ // direct creation
+ public static String create_failedExtensionPoint;
+
+ // executable extensions
+ public static String exExt_findClassError;
+ public static String exExt_instantiateClassError;
+ public static String exExt_initObjectError;
+ public static String exExt_extDefNotFound;
+
+ // plugins
+ public static String plugin_eventListenerError;
+ public static String plugin_initObjectError;
+ public static String plugin_instantiateClassError;
+ public static String plugin_loadClassError;
+
+ // logging
+ public static String log_error;
+ public static String log_warning;
+ public static String log_log;
+
+ static {
+ // load message values from bundle file
+ reloadMessages();
+ }
+
+ public static void reloadMessages() {
+ NLS.initializeMessages(BUNDLE_NAME, RegistryMessages.class);
+ }
+} \ No newline at end of file
diff --git a/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/RegistryObject.java b/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/RegistryObject.java
new file mode 100644
index 000000000..7ba05f570
--- /dev/null
+++ b/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/RegistryObject.java
@@ -0,0 +1,62 @@
+/*******************************************************************************
+ * 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.registry;
+
+/**
+ * An object which has the general characteristics of all the nestable elements
+ * in a plug-in manifest.
+ */
+public abstract class RegistryObject implements KeyedElement {
+ //Object identifier
+ private int objectId = RegistryObjectManager.UNKNOWN;
+ //The children of the element
+ protected int[] children = RegistryObjectManager.EMPTY_INT_ARRAY;
+ //The position of the extra data when available
+ protected int extraDataOffset = -1;
+
+ //The registry that owns this object
+ protected ExtensionRegistry registry;
+
+ protected RegistryObject(ExtensionRegistry registry) {
+ this.registry = registry;
+ }
+
+ void setRawChildren(int[] values) {
+ children = values;
+ }
+
+ //This can not return null. It returns the singleton empty array or an array
+ protected int[] getRawChildren() {
+ return children;
+ }
+
+ void setObjectId(int value) {
+ objectId = value;
+ }
+
+ protected int getObjectId() {
+ return objectId;
+ }
+
+ //Implementation of the KeyedElement interface
+ public int getKeyHashCode() {
+ return objectId;
+ }
+
+ public Object getKey() {
+ return new Integer(objectId);
+ }
+
+ public boolean compare(KeyedElement other) {
+ return objectId == ((RegistryObject) other).objectId;
+ }
+
+}
diff --git a/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/RegistryObjectFactory.java b/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/RegistryObjectFactory.java
new file mode 100644
index 000000000..aa4679d29
--- /dev/null
+++ b/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/RegistryObjectFactory.java
@@ -0,0 +1,60 @@
+/*******************************************************************************
+ * 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.registry;
+
+/**
+ * A factory method for the creation of the registry objects.
+ */
+public class RegistryObjectFactory {
+
+ // The extension registry that this element factory works in
+ protected ExtensionRegistry registry;
+
+ public RegistryObjectFactory(ExtensionRegistry registry) {
+ this.registry = registry;
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Contribution
+ public Contribution createContribution(long contributorId) {
+ return new Contribution(contributorId, registry);
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Extension point
+ public ExtensionPoint createExtensionPoint() {
+ return new ExtensionPoint(registry);
+ }
+
+ public ExtensionPoint createExtensionPoint(int self, int[] children, int dataOffset) {
+ return new ExtensionPoint(self, children, dataOffset, registry);
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Extension
+ public Extension createExtension() {
+ return new Extension(registry);
+ }
+
+ public Extension createExtension(int self, String simpleId, String namespace, int[] children, int extraData) {
+ return new Extension(self, simpleId, namespace, children, extraData, registry);
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Configuration element
+ public ConfigurationElement createConfigurationElement() {
+ return new ConfigurationElement(registry);
+ }
+
+ public ConfigurationElement createConfigurationElement(int self, long contributorId, long namespaceOwnerId, String name, String[] propertiesAndValue, int[] children, int extraDataOffset, int parent, byte parentType) {
+ return new ConfigurationElement(self, contributorId, namespaceOwnerId, name, propertiesAndValue, children, extraDataOffset, parent, parentType, registry);
+ }
+}
diff --git a/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/RegistryObjectManager.java b/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/RegistryObjectManager.java
new file mode 100644
index 000000000..98b96c266
--- /dev/null
+++ b/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/RegistryObjectManager.java
@@ -0,0 +1,560 @@
+/*******************************************************************************
+ * 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.registry;
+
+import java.lang.ref.SoftReference;
+import java.util.*;
+import org.eclipse.core.runtime.InvalidRegistryObjectException;
+
+/**
+ * This class manage all the object from the registry but does not deal with their dependencies.
+ * It serves the objects which are either directly obtained from memory or read from a cache.
+ * It also returns handles for objects.
+ */
+public class RegistryObjectManager implements IObjectManager {
+ //Constants used to get the objects and their handles
+ static public final byte CONFIGURATION_ELEMENT = 1;
+ static public final byte EXTENSION = 2;
+ static public final byte EXTENSION_POINT = 3;
+ static public final byte THIRDLEVEL_CONFIGURATION_ELEMENT = 4;
+
+ static final int CACHE_INITIAL_SIZE = 512; //This value has been picked because it is the minimal size required to startup an RCP app. (FYI, eclipse requires 3 growths).
+ static final float DEFAULT_LOADFACTOR = 0.75f; //This is the default factor used in reference map.
+
+ static final int[] EMPTY_INT_ARRAY = new int[0];
+ static final String[] EMPTY_STRING_ARRAY = new String[0];
+
+ static int UNKNOWN = -1;
+
+ // key: extensionPointName, value: object id
+ private HashtableOfStringAndInt extensionPoints; //This is loaded on startup. Then entries can be added when loading a new plugin from the xml.
+ // key: object id, value: an object
+ private ReferenceMap cache; //Entries are added by getter. The structure is not thread safe.
+ //key: int, value: int
+ private HashtableOfInt fileOffsets; //This is read once on startup when loading from the cache. Entries are never added here. They are only removed to prevent "removed" objects to be reloaded.
+
+ private int nextId = 1; //This is only used to get the next number available.
+
+ //Those two data structures are only used when the addition or the removal of a plugin occurs.
+ //They are used to keep track on a contributor basis of the extension being added or removed
+ private KeyedHashSet newContributions; //represents the contributers added during this session.
+ private Object formerContributions; //represents the contributers encountered in previous sessions. This is loaded lazily.
+
+ // Map key: extensionPointFullyQualifiedName, value int[] of orphan extensions.
+ // The orphan access does not need to be synchronized because the it is protected by the lock in extension registry.
+ private Object orphanExtensions;
+
+ private KeyedHashSet heldObjects = new KeyedHashSet(); //strong reference to the objects that must be hold on to
+
+ //Indicate if objects have been removed or added from the table. This only needs to be set in a couple of places (addNamespace and removeNamespace)
+ private boolean isDirty = false;
+
+ private boolean fromCache = false;
+
+ private ExtensionRegistry registry;
+
+ // TODO this option is not used
+ // OSGI system properties. Copied from EclipseStarter
+ public static final String PROP_NO_REGISTRY_FLUSHING = "eclipse.noRegistryFlushing"; //$NON-NLS-1$
+
+ public RegistryObjectManager(ExtensionRegistry registry) {
+ extensionPoints = new HashtableOfStringAndInt();
+ if ("true".equalsIgnoreCase(System.getProperty(PROP_NO_REGISTRY_FLUSHING))) { //$NON-NLS-1$
+ cache = new ReferenceMap(ReferenceMap.HARD, CACHE_INITIAL_SIZE, DEFAULT_LOADFACTOR);
+ } else {
+ cache = new ReferenceMap(ReferenceMap.SOFT, CACHE_INITIAL_SIZE, DEFAULT_LOADFACTOR);
+ }
+ newContributions = new KeyedHashSet();
+ fileOffsets = new HashtableOfInt();
+
+ this.registry = registry;
+ }
+
+ /**
+ * Initialize the object manager. Return true if the initialization succeeded, false otherwise
+ */
+ synchronized boolean init(long timeStamp) {
+ TableReader reader = registry.getCleanTableReader();
+ Object[] results = reader.loadTables(timeStamp);
+ if (results == null) {
+ return false;
+ }
+ fileOffsets = (HashtableOfInt) results[0];
+ extensionPoints = (HashtableOfStringAndInt) results[1];
+ nextId = ((Integer) results[2]).intValue();
+ fromCache = true;
+
+ if (!registry.useLazyCacheLoading()) {
+ //TODO Here we could grow all the tables to the right size (ReferenceMap)
+ reader.setHoldObjects(true);
+ markOrphansHasDirty(getOrphans());
+ fromCache = reader.readAllCache(this);
+ formerContributions = getFormerContributions();
+ }
+ return fromCache;
+ }
+
+ synchronized void addContribution(Contribution contribution) {
+ isDirty = true;
+ newContributions.add(contribution);
+ }
+
+ synchronized int[] getExtensionPointsFrom(long id) {
+ KeyedElement tmp = newContributions.getByKey(new Long(id));
+ if (tmp == null)
+ tmp = getFormerContributions().getByKey(new Long(id));
+ if (tmp == null)
+ return EMPTY_INT_ARRAY;
+ return ((Contribution) tmp).getExtensionPoints();
+ }
+
+ synchronized Set getNamespaces() {
+ KeyedElement[] formerElts = getFormerContributions().elements();
+ KeyedElement[] newElts = newContributions.elements();
+ Set tmp = new HashSet(formerElts.length + newElts.length);
+ for (int i = 0; i < formerElts.length; i++) {
+ tmp.add(((Contribution) formerElts[i]).getNamespace());
+ }
+ for (int i = 0; i < newElts.length; i++) {
+ tmp.add(((Contribution) newElts[i]).getNamespace());
+ }
+ return tmp;
+ }
+
+ synchronized boolean hasContribution(long id) {
+ Object result = newContributions.getByKey(new Long(id));
+ if (result == null)
+ result = getFormerContributions().getByKey(new Long(id));
+ return result != null;
+ }
+
+ private KeyedHashSet getFormerContributions() {
+ KeyedHashSet result;
+ if (fromCache == false)
+ return new KeyedHashSet(0);
+
+ if (formerContributions == null || (result = ((KeyedHashSet) ((formerContributions instanceof SoftReference) ? ((SoftReference) formerContributions).get() : formerContributions))) == null) {
+ TableReader reader = registry.getCleanTableReader();
+ result = reader.loadNamespaces();
+ formerContributions = new SoftReference(result);
+ }
+ return result;
+ }
+
+ synchronized public void add(RegistryObject registryObject, boolean hold) {
+ if (registryObject.getObjectId() == UNKNOWN) {
+ int id = nextId++;
+ registryObject.setObjectId(id);
+ }
+ cache.put(registryObject.getObjectId(), registryObject);
+ if (hold)
+ hold(registryObject);
+ }
+
+ private void remove(RegistryObject registryObject, boolean release) {
+ cache.remove(registryObject.getObjectId());
+ if (release)
+ release(registryObject);
+ }
+
+ synchronized void remove(int id, boolean release) {
+ RegistryObject toRemove = (RegistryObject) cache.get(id);
+ if (fileOffsets != null)
+ fileOffsets.removeKey(id);
+ if (toRemove != null)
+ remove(toRemove, release);
+ }
+
+ private void hold(RegistryObject toHold) {
+ heldObjects.add(toHold);
+ }
+
+ private void release(RegistryObject toRelease) {
+ heldObjects.remove(toRelease);
+ }
+
+ public synchronized Object getObject(int id, byte type) {
+ return basicGetObject(id, type);
+ }
+
+ private Object basicGetObject(int id, byte type) {
+ Object result = cache.get(id);
+ if (result != null)
+ return result;
+ if (fromCache)
+ result = load(id, type);
+ if (result == null)
+ throw new InvalidRegistryObjectException();
+ cache.put(id, result);
+ return result;
+ }
+
+ public synchronized RegistryObject[] getObjects(int[] values, byte type) {
+ if (values.length == 0) {
+ switch (type) {
+ case EXTENSION_POINT :
+ return ExtensionPoint.EMPTY_ARRAY;
+ case EXTENSION :
+ return Extension.EMPTY_ARRAY;
+ case CONFIGURATION_ELEMENT :
+ case THIRDLEVEL_CONFIGURATION_ELEMENT :
+ return ConfigurationElement.EMPTY_ARRAY;
+ }
+ }
+
+ RegistryObject[] results = null;
+ switch (type) {
+ case EXTENSION_POINT :
+ results = new ExtensionPoint[values.length];
+ break;
+ case EXTENSION :
+ results = new Extension[values.length];
+ break;
+ case CONFIGURATION_ELEMENT :
+ case THIRDLEVEL_CONFIGURATION_ELEMENT :
+ results = new ConfigurationElement[values.length];
+ break;
+ }
+ for (int i = 0; i < values.length; i++) {
+ results[i] = (RegistryObject) basicGetObject(values[i], type);
+ }
+ return results;
+ }
+
+ synchronized ExtensionPoint getExtensionPointObject(String xptUniqueId) {
+ int id;
+ if ((id = extensionPoints.get(xptUniqueId)) == HashtableOfStringAndInt.MISSING_ELEMENT)
+ return null;
+ return (ExtensionPoint) getObject(id, EXTENSION_POINT);
+ }
+
+ public Handle getHandle(int id, byte type) {
+ switch (type) {
+ case EXTENSION_POINT :
+ return new ExtensionPointHandle(this, id);
+
+ case EXTENSION :
+ return new ExtensionHandle(this, id);
+
+ case CONFIGURATION_ELEMENT :
+ return new ConfigurationElementHandle(this, id);
+
+ case THIRDLEVEL_CONFIGURATION_ELEMENT :
+ default : //avoid compiler error, type should always be known
+ return new ThirdLevelConfigurationElementHandle(this, id);
+ }
+ }
+
+ public Handle[] getHandles(int[] ids, byte type) {
+ Handle[] results = null;
+ int nbrId = ids.length;
+ switch (type) {
+ case EXTENSION_POINT :
+ if (nbrId == 0)
+ return ExtensionPointHandle.EMPTY_ARRAY;
+ results = new ExtensionPointHandle[nbrId];
+ for (int i = 0; i < nbrId; i++) {
+ results[i] = new ExtensionPointHandle(this, ids[i]);
+ }
+ break;
+
+ case EXTENSION :
+ if (nbrId == 0)
+ return ExtensionHandle.EMPTY_ARRAY;
+ results = new ExtensionHandle[nbrId];
+ for (int i = 0; i < nbrId; i++) {
+ results[i] = new ExtensionHandle(this, ids[i]);
+ }
+ break;
+
+ case CONFIGURATION_ELEMENT :
+ if (nbrId == 0)
+ return ConfigurationElementHandle.EMPTY_ARRAY;
+ results = new ConfigurationElementHandle[nbrId];
+ for (int i = 0; i < nbrId; i++) {
+ results[i] = new ConfigurationElementHandle(this, ids[i]);
+ }
+ break;
+
+ case THIRDLEVEL_CONFIGURATION_ELEMENT :
+ if (nbrId == 0)
+ return ConfigurationElementHandle.EMPTY_ARRAY;
+ results = new ThirdLevelConfigurationElementHandle[nbrId];
+ for (int i = 0; i < nbrId; i++) {
+ results[i] = new ThirdLevelConfigurationElementHandle(this, ids[i]);
+ }
+ break;
+ }
+ return results;
+ }
+
+ synchronized ExtensionPointHandle[] getExtensionPointsHandles() {
+ return (ExtensionPointHandle[]) getHandles(extensionPoints.getValues(), EXTENSION_POINT);
+ }
+
+ synchronized ExtensionPointHandle getExtensionPointHandle(String xptUniqueId) {
+ int id = extensionPoints.get(xptUniqueId);
+ if (id == HashtableOfStringAndInt.MISSING_ELEMENT)
+ return null;
+ return (ExtensionPointHandle) getHandle(id, EXTENSION_POINT);
+ }
+
+ private Object load(int id, byte type) {
+ TableReader reader = registry.getCleanTableReader();
+ int offset = fileOffsets.get(id);
+ if (offset == Integer.MIN_VALUE)
+ return null;
+ switch (type) {
+ case CONFIGURATION_ELEMENT :
+ return reader.loadConfigurationElement(offset);
+
+ case THIRDLEVEL_CONFIGURATION_ELEMENT :
+ return reader.loadThirdLevelConfigurationElements(offset, this);
+
+ case EXTENSION :
+ return reader.loadExtension(offset);
+
+ case EXTENSION_POINT :
+ default : //avoid compile errors. type must always be known
+ return reader.loadExtensionPointTree(offset, this);
+ }
+ }
+
+ synchronized int[] getExtensionsFrom(long contributorId) {
+ KeyedElement tmp = newContributions.getByKey(new Long(contributorId));
+ if (tmp == null)
+ tmp = getFormerContributions().getByKey(new Long(contributorId));
+ if (tmp == null)
+ return EMPTY_INT_ARRAY;
+ return ((Contribution) tmp).getExtensions();
+ }
+
+ synchronized void addExtensionPoint(ExtensionPoint currentExtPoint, boolean hold) {
+ add(currentExtPoint, hold);
+ extensionPoints.put(currentExtPoint.getUniqueIdentifier(), currentExtPoint.getObjectId());
+ }
+
+ synchronized void removeExtensionPoint(String extensionPointId) {
+ int pointId = extensionPoints.removeKey(extensionPointId);
+ if (pointId == HashtableOfStringAndInt.MISSING_ELEMENT)
+ return;
+ remove(pointId, true);
+ }
+
+ public boolean isDirty() {
+ return isDirty;
+ }
+
+ synchronized void removeContribution(long contributorId) {
+ boolean removed = newContributions.removeByKey(new Long(contributorId));
+ if (removed == false) {
+ removed = getFormerContributions().removeByKey(new Long(contributorId));
+ if (removed)
+ formerContributions = getFormerContributions(); //This forces the removed namespace to stay around, so we do not forget about removed namespaces
+ }
+
+ if (removed) {
+ isDirty = true;
+ return;
+ }
+
+ }
+
+ private Map getOrphans() {
+ Object result = orphanExtensions;
+ if (orphanExtensions == null && !fromCache) {
+ result = new HashMap();
+ orphanExtensions = result;
+ } else if (orphanExtensions == null || (result = ((HashMap) ((orphanExtensions instanceof SoftReference) ? ((SoftReference) orphanExtensions).get() : orphanExtensions))) == null) {
+ TableReader reader = registry.getCleanTableReader();
+ result = reader.loadOrphans();
+ orphanExtensions = new SoftReference(result);
+ }
+ return (HashMap) result;
+ }
+
+ void addOrphans(String extensionPoint, int[] extensions) {
+ Map orphans = getOrphans();
+ int[] existingOrphanExtensions = (int[]) orphans.get(extensionPoint);
+
+ if (existingOrphanExtensions != null) {
+ // just add
+ int[] newOrphanExtensions = new int[existingOrphanExtensions.length + extensions.length];
+ System.arraycopy(existingOrphanExtensions, 0, newOrphanExtensions, 0, existingOrphanExtensions.length);
+ System.arraycopy(extensions, 0, newOrphanExtensions, existingOrphanExtensions.length, extensions.length);
+ orphans.put(extensionPoint, newOrphanExtensions);
+ } else {
+ // otherwise this is the first one
+ orphans.put(extensionPoint, extensions);
+ }
+ markOrphansHasDirty(orphans);
+ }
+
+ void markOrphansHasDirty(Map orphans) {
+ orphanExtensions = orphans;
+ }
+
+ void addOrphan(String extensionPoint, int extension) {
+ Map orphans = getOrphans();
+ int[] existingOrphanExtensions = (int[]) orphans.get(extensionPoint);
+
+ if (existingOrphanExtensions != null) {
+ // just add
+ int[] newOrphanExtensions = new int[existingOrphanExtensions.length + 1];
+ System.arraycopy(existingOrphanExtensions, 0, newOrphanExtensions, 0, existingOrphanExtensions.length);
+ newOrphanExtensions[existingOrphanExtensions.length] = extension;
+ orphans.put(extensionPoint, newOrphanExtensions);
+ } else {
+ // otherwise this is the first one
+ orphans.put(extensionPoint, new int[] {extension});
+ }
+ markOrphansHasDirty(orphans);
+ }
+
+ int[] removeOrphans(String extensionPoint) {
+ Map orphans = getOrphans();
+ int[] existingOrphanExtensions = (int[]) orphans.remove(extensionPoint);
+ if (existingOrphanExtensions != null) {
+ markOrphansHasDirty(orphans);
+ }
+ return existingOrphanExtensions;
+ }
+
+ void removeOrphan(String extensionPoint, int extension) {
+ Map orphans = getOrphans();
+ int[] existingOrphanExtensions = (int[]) orphans.get(extensionPoint);
+
+ if (existingOrphanExtensions == null)
+ return;
+
+ markOrphansHasDirty(orphans);
+ int newSize = existingOrphanExtensions.length - 1;
+ if (newSize == 0) {
+ orphans.remove(extensionPoint);
+ return;
+ }
+
+ int[] newOrphanExtensions = new int[existingOrphanExtensions.length - 1];
+ for (int i = 0, j = 0; i < existingOrphanExtensions.length; i++)
+ if (extension != existingOrphanExtensions[i])
+ newOrphanExtensions[j++] = existingOrphanExtensions[i];
+
+ orphans.put(extensionPoint, newOrphanExtensions);
+ return;
+ }
+
+ //This method is only used by the writer to reach in
+ Map getOrphanExtensions() {
+ return getOrphans();
+ }
+
+ // This method is only used by the writer to reach in
+ int getNextId() {
+ return nextId;
+ }
+
+ // This method is only used by the writer to reach in
+ HashtableOfStringAndInt getExtensionPoints() {
+ return extensionPoints;
+ }
+
+ // This method is only used by the writer to reach in
+ KeyedHashSet[] getContributions() {
+ return new KeyedHashSet[] {newContributions, getFormerContributions()};
+ }
+
+ /**
+ * Collect all the objects that are removed by this operation and store
+ * them in a IObjectManager so that they can be accessed from the appropriate
+ * deltas but not from the registry.
+ */
+ synchronized Map getAssociatedObjects(long contributionId) {
+ //Collect all the objects associated with this contribution
+ int[] xpts = getExtensionPointsFrom(contributionId);
+ int[] exts = getExtensionsFrom(contributionId);
+ Map actualObjects = new HashMap(xpts.length + exts.length);
+ for (int i = 0; i < exts.length; i++) {
+ Extension tmp = (Extension) basicGetObject(exts[i], RegistryObjectManager.EXTENSION);
+ actualObjects.put(new Integer(exts[i]), tmp);
+ collectChildren(tmp, 0, actualObjects);
+ }
+ for (int i = 0; i < xpts.length; i++) {
+ ExtensionPoint xpt = (ExtensionPoint) basicGetObject(xpts[i], RegistryObjectManager.EXTENSION_POINT);
+ actualObjects.put(new Integer(xpts[i]), xpt);
+ }
+
+ return actualObjects;
+ }
+
+ /**
+ * Add to the set of the objects all extensions and extension points that
+ * could be navigated to from the objects in the set.
+ */
+ synchronized void addNavigableObjects(Map associatedObjects) {
+ Map result = new HashMap();
+ for (Iterator iter = associatedObjects.values().iterator(); iter.hasNext();) {
+ RegistryObject object = (RegistryObject) iter.next();
+ if (object instanceof Extension) {
+ // add extension point
+ ExtensionPoint extPoint = getExtensionPointObject(((Extension) object).getExtensionPointIdentifier());
+ if (extPoint == null) // already removed?
+ continue;
+
+ Integer extPointIndex = new Integer(extPoint.getKeyHashCode());
+ if (!associatedObjects.containsKey(extPointIndex))
+ result.put(new Integer(extPoint.getKeyHashCode()), extPoint);
+
+ // add all extensions for the extension point
+ int[] extensions = extPoint.getRawChildren();
+ for (int j = 0; j < extensions.length; j++) {
+ Extension tmp = (Extension) basicGetObject(extensions[j], RegistryObjectManager.EXTENSION);
+ if (tmp == null) // already removed
+ continue;
+ Integer extensionIndex = new Integer(extensions[j]);
+ if (!associatedObjects.containsKey(extensionIndex))
+ result.put(extensionIndex, tmp);
+ }
+ }
+ }
+ associatedObjects.putAll(result);
+ }
+
+ synchronized void removeObjects(Map associatedObjects) {
+ //Remove the objects from the main object manager so they can no longer be accessed.
+ Collection allValues = associatedObjects.values();
+ for (Iterator iter = allValues.iterator(); iter.hasNext();) {
+ RegistryObject toRemove = (RegistryObject) iter.next();
+ remove((toRemove).getObjectId(), true);
+ if (toRemove instanceof ExtensionPoint)
+ removeExtensionPoint(((ExtensionPoint) toRemove).getUniqueIdentifier());
+ }
+ }
+
+ IObjectManager createDelegatingObjectManager(Map object) {
+ return new TemporaryObjectManager(object, this);
+ }
+
+ private void collectChildren(RegistryObject ce, int level, Map collector) {
+ ConfigurationElement[] children = (ConfigurationElement[]) getObjects(ce.getRawChildren(), level == 0 || ce.extraDataOffset == -1 ? RegistryObjectManager.CONFIGURATION_ELEMENT : RegistryObjectManager.THIRDLEVEL_CONFIGURATION_ELEMENT);
+ for (int j = 0; j < children.length; j++) {
+ collector.put(new Integer(children[j].getObjectId()), children[j]);
+ collectChildren(children[j], level + 1, collector);
+ }
+ }
+
+ public void close() {
+ //do nothing.
+ }
+
+ public ExtensionRegistry getRegistry() {
+ return registry;
+ }
+}
diff --git a/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/RegistrySupport.java b/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/RegistrySupport.java
new file mode 100644
index 000000000..3c1819a9f
--- /dev/null
+++ b/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/RegistrySupport.java
@@ -0,0 +1,76 @@
+/*******************************************************************************
+ * 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.registry;
+
+import java.util.ResourceBundle;
+import org.eclipse.core.runtime.IStatus;
+
+/**
+ * Simple implementation if the registry support functionality.
+ * The logging output is done onto System.out (for both specific and generic logs)
+ * in the following format:
+ *
+ * [Error|Warning|Log]: Main error message
+ * [Error|Warning|Log]: Child error message 1
+ * ...
+ * [Error|Warning|Log]: Child error message N
+ *
+ * The translation routine assumes that keys are prefixed with '%'. If no resource
+ * bundle is present, the key itself (without leading '%') is returned. There is
+ * no decoding for the leading '%%'.
+ */
+public class RegistrySupport {
+
+ static public String translate(String key, ResourceBundle resources) {
+ String value = key.trim();
+ if (value.charAt(0) != '%')
+ return value;
+ if (resources == null)
+ return key;
+ return resources.getString(key.substring(1));
+ }
+
+ static public void log(IStatus status, String prefix) {
+ String message = status.getMessage();
+ int severity = status.getSeverity();
+
+ String statusMsg;
+ switch (severity) {
+ case IStatus.ERROR :
+ statusMsg = RegistryMessages.log_error;
+ break;
+ case IStatus.WARNING :
+ statusMsg = RegistryMessages.log_warning;
+ break;
+ default :
+ statusMsg = RegistryMessages.log_log;
+ break;
+ }
+ statusMsg += message;
+
+ if (prefix != null)
+ statusMsg = prefix + statusMsg;
+ System.out.println(statusMsg);
+
+ // print out children as well
+ IStatus[] children = status.getChildren();
+ if (children.length != 0) {
+ String newPrefix;
+ if (prefix == null)
+ newPrefix = "\t"; //$NON-NLS-1$
+ else
+ newPrefix = prefix + "\t"; //$NON-NLS-1$
+ for (int i = 0; i < children.length; i++) {
+ log(children[i], newPrefix);
+ }
+ }
+ }
+}
diff --git a/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/TableReader.java b/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/TableReader.java
new file mode 100644
index 000000000..81fda00ef
--- /dev/null
+++ b/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/TableReader.java
@@ -0,0 +1,554 @@
+/*******************************************************************************
+ * 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.registry;
+
+import java.io.*;
+import java.util.HashMap;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.osgi.util.NLS;
+
+public class TableReader {
+ //Markers in the cache
+ static final int NULL = 0;
+ static final int OBJECT = 1;
+
+ //The version of the cache
+ static final int CACHE_VERSION = 1;
+
+ //Informations representing the MAIN file
+ static final String MAIN = ".mainData"; //$NON-NLS-1$
+ File mainDataFile;
+ DataInputStream mainInput = null;
+ // int size;
+
+ //Informations representing the EXTRA file
+ static final String EXTRA = ".extraData"; //$NON-NLS-1$
+ File extraDataFile;
+ DataInputStream extraInput = null;
+ // int sizeExtra;
+
+ //The table file
+ static final String TABLE = ".table"; //$NON-NLS-1$
+ File tableFile;
+
+ //The namespace file
+ static final String CONTRIBUTIONS = ".contributions"; //$NON-NLS-1$
+ File contributionsFile;
+
+ //The orphan file
+ static final String ORPHANS = ".orphans"; //$NON-NLS-1$
+ File orphansFile;
+
+ //Status code
+ private static final byte fileError = 0;
+ private static final boolean DEBUG = false; //TODO need to change
+
+ private boolean holdObjects = false;
+
+ private ExtensionRegistry registry;
+
+ void setMainDataFile(File main) {
+ mainDataFile = main;
+ }
+
+ void setExtraDataFile(File extra) {
+ extraDataFile = extra;
+ }
+
+ void setTableFile(File table) {
+ tableFile = table;
+ }
+
+ void setContributionsFile(File namespace) {
+ contributionsFile = namespace;
+ }
+
+ void setOrphansFile(File orphan) {
+ orphansFile = orphan;
+ }
+
+ public TableReader(ExtensionRegistry registry) {
+ this.registry = registry;
+ }
+
+ private void openInputFile() {
+ try {
+ mainInput = new DataInputStream(new BufferedInputStream(new FileInputStream(mainDataFile)));
+ } catch (FileNotFoundException e) {
+ log(new Status(IStatus.ERROR, RegistryMessages.OWNER_NAME, fileError, RegistryMessages.meta_unableToReadCache, e));
+ registry.clearRegistryCache();
+ throw new IllegalStateException(RegistryMessages.meta_registryCacheReadProblems);
+ }
+ }
+
+ private void openExtraFile() {
+ try {
+ extraInput = new DataInputStream(new BufferedInputStream(new FileInputStream(extraDataFile)));
+ } catch (FileNotFoundException e) {
+ log(new Status(IStatus.ERROR, RegistryMessages.OWNER_NAME, fileError, RegistryMessages.meta_unableToReadCache, e));
+ registry.clearRegistryCache();
+ throw new IllegalStateException(RegistryMessages.meta_registryCacheReadProblems);
+ }
+ }
+
+ private void closeInputFile() {
+ try {
+ mainInput.close();
+ } catch (IOException e) {
+ log(new Status(IStatus.ERROR, RegistryMessages.OWNER_NAME, fileError, RegistryMessages.meta_registryCacheReadProblems, e));
+ }
+
+ }
+
+ private void closeExtraFile() {
+ try {
+ extraInput.close();
+ } catch (IOException e) {
+ log(new Status(IStatus.ERROR, RegistryMessages.OWNER_NAME, fileError, RegistryMessages.meta_registryCacheReadProblems, e));
+ }
+
+ }
+
+ /**
+ * Resets position in the main input file and extra input files to the
+ * begining of the stream; sets holdObjects flag to a default value (false).
+ */
+ public void reset() {
+ if (extraInput != null)
+ closeExtraFile();
+ if (mainInput != null)
+ closeInputFile();
+
+ holdObjects = false;
+
+ openInputFile();
+ openExtraFile();
+ }
+
+ public Object[] loadTables(long expectedTimestamp) {
+ HashtableOfInt offsets;
+ HashtableOfStringAndInt extensionPoints;
+
+ DataInputStream tableInput = null;
+ try {
+ tableInput = new DataInputStream(new BufferedInputStream(new FileInputStream(tableFile)));
+ if (!checkCacheValidity(tableInput, expectedTimestamp))
+ return null;
+
+ Integer nextId = new Integer(tableInput.readInt());
+ offsets = new HashtableOfInt();
+ offsets.load(tableInput);
+ extensionPoints = new HashtableOfStringAndInt();
+ extensionPoints.load(tableInput);
+ return new Object[] {offsets, extensionPoints, nextId};
+ } catch (IOException e) {
+ if (tableInput != null)
+ try {
+ tableInput.close();
+ } catch (IOException e1) {
+ //Ignore
+ }
+ log(new Status(IStatus.ERROR, RegistryMessages.OWNER_NAME, fileError, RegistryMessages.meta_registryCacheReadProblems, e));
+ return null;
+ }
+
+ }
+
+ // Check various aspect of the cache to see if it's valid
+ private boolean checkCacheValidity(DataInputStream in, long expectedTimestamp) {
+ int version;
+ try {
+ version = in.readInt();
+ if (version != CACHE_VERSION)
+ return false;
+
+ long installStamp = in.readLong();
+ long registryStamp = in.readLong();
+ long mainDataFileSize = in.readLong();
+ long extraDataFileSize = in.readLong();
+ long contributionsFileSize = in.readLong();
+ long orphansFileSize = in.readLong();
+ String osStamp = in.readUTF();
+ String windowsStamp = in.readUTF();
+ String localeStamp = in.readUTF();
+ long timeStamp = registry.computeState();
+ return ((expectedTimestamp == 0 || expectedTimestamp == registryStamp) && (installStamp == timeStamp) && (osStamp.equals(System.getProperty(IRegistryConstants.PROP_OS))) && (windowsStamp.equals(System.getProperty(IRegistryConstants.PROP_WS))) && (localeStamp.equals(System.getProperty(IRegistryConstants.PROP_NL))) && mainDataFileSize == mainDataFile.length() && extraDataFileSize == extraDataFile.length() && contributionsFileSize == contributionsFile.length() && orphansFileSize == orphansFile.length());
+ } catch (IOException e) {
+ log(new Status(IStatus.ERROR, RegistryMessages.OWNER_NAME, fileError, RegistryMessages.meta_registryCacheInconsistent, e));
+ return false;
+ }
+ }
+
+ public Object loadConfigurationElement(int offset) {
+ try {
+ goToInputFile(offset);
+ return basicLoadConfigurationElement(mainInput, -1);
+ } catch (IOException e) {
+ String message = NLS.bind(RegistryMessages.meta_regCacheIOExceptionReading, mainDataFile);
+ log(new Status(IStatus.ERROR, RegistryMessages.OWNER_NAME, fileError, message, e));
+ if (DEBUG)
+ log(new Status(IStatus.ERROR, RegistryMessages.OWNER_NAME, fileError, "Error reading a configuration element (" + offset + ") from the registry cache", e)); //$NON-NLS-1$//$NON-NLS-2$
+ return null;
+ } finally {
+ closeInputFile();
+ closeExtraFile();
+ }
+ }
+
+ private ConfigurationElement basicLoadConfigurationElement(DataInputStream is, long namespaceOwnerId) throws IOException {
+ int self = is.readInt();
+ long contributorId = is.readLong();
+ String name = readStringOrNull(is, false);
+ int parentId = is.readInt();
+ byte parentType = is.readByte();
+ int misc = is.readInt();//this is set in second level CEs, to indicate where in the extra data file the children ces are
+ String[] propertiesAndValue = readPropertiesAndValue(is);
+ int[] children = readArray(is);
+ return getObjectFactory().createConfigurationElement(self, contributorId, namespaceOwnerId, name, propertiesAndValue, children, misc, parentId, parentType);
+ }
+
+ public Object loadThirdLevelConfigurationElements(int offset, RegistryObjectManager objectManager) {
+ try {
+ goToExtraFile(offset);
+ return loadConfigurationElementAndChildren(null, extraInput, 3, Integer.MAX_VALUE, objectManager, -1);
+ } catch (IOException e) {
+ String message = NLS.bind(RegistryMessages.meta_regCacheIOExceptionReading, extraDataFile);
+ log(new Status(IStatus.ERROR, RegistryMessages.OWNER_NAME, fileError, message, e));
+ if (DEBUG)
+ log(new Status(IStatus.ERROR, RegistryMessages.OWNER_NAME, fileError, "Error reading a third level configuration element (" + offset + ") from the registry cache", e)); //$NON-NLS-1$//$NON-NLS-2$
+ return null;
+ } finally {
+ closeInputFile();
+ closeExtraFile();
+ }
+ }
+
+ //Read a whole configuration element subtree
+ private ConfigurationElement loadConfigurationElementAndChildren(DataInputStream is, DataInputStream extraIs, int depth, int maxDepth, RegistryObjectManager objectManager, long namespaceOwnerId) throws IOException {
+ DataInputStream currentStream = is;
+ if (depth > 2)
+ currentStream = extraIs;
+
+ ConfigurationElement ce = basicLoadConfigurationElement(currentStream, namespaceOwnerId);
+ if (namespaceOwnerId == -1)
+ namespaceOwnerId = ce.getNamespaceOwnerId();
+ int[] children = ce.getRawChildren();
+ if (depth + 1 > maxDepth)
+ return ce;
+
+ for (int i = 0; i < children.length; i++) {
+ ConfigurationElement tmp = loadConfigurationElementAndChildren(currentStream, extraIs, depth + 1, maxDepth, objectManager, namespaceOwnerId);
+ objectManager.add(tmp, holdObjects);
+ }
+ return ce;
+ }
+
+ private String[] readPropertiesAndValue(DataInputStream inputStream) throws IOException {
+ int numberOfProperties = inputStream.readInt();
+ if (numberOfProperties == 0)
+ return RegistryObjectManager.EMPTY_STRING_ARRAY;
+ String[] properties = new String[numberOfProperties];
+ for (int i = 0; i < numberOfProperties; i++) {
+ properties[i] = readStringOrNull(inputStream, false);
+ }
+ return properties;
+ }
+
+ public Object loadExtension(int offset) {
+ try {
+ goToInputFile(offset);
+ return basicLoadExtension(mainInput);
+ } catch (IOException e) {
+ String message = NLS.bind(RegistryMessages.meta_regCacheIOExceptionReading, mainDataFile);
+ log(new Status(IStatus.ERROR, RegistryMessages.OWNER_NAME, fileError, message, e));
+ if (DEBUG)
+ log(new Status(IStatus.ERROR, RegistryMessages.OWNER_NAME, fileError, "Error reading an extension (" + offset + ") from the registry cache", e)); //$NON-NLS-1$//$NON-NLS-2$
+ } finally {
+ closeExtraFile();
+ closeInputFile();
+ }
+ return null;
+ }
+
+ private Extension basicLoadExtension(DataInputStream inputStream) throws IOException {
+ int self = inputStream.readInt();
+ String simpleId = readStringOrNull(mainInput, false);
+ String namespace = readStringOrNull(mainInput, false);
+ int[] children = readArray(mainInput);
+ int extraData = mainInput.readInt();
+ return getObjectFactory().createExtension(self, simpleId, namespace, children, extraData);
+ }
+
+ public ExtensionPoint loadExtensionPointTree(int offset, RegistryObjectManager objects) {
+ try {
+ ExtensionPoint xpt = (ExtensionPoint) loadExtensionPoint(offset);
+ int[] children = xpt.getRawChildren();
+ int nbrOfExtension = children.length;
+ for (int i = 0; i < nbrOfExtension; i++) {
+ Extension loaded = basicLoadExtension(mainInput);
+ objects.add(loaded, holdObjects);
+ }
+
+ for (int i = 0; i < nbrOfExtension; i++) {
+ int nbrOfCe = mainInput.readInt();
+ for (int j = 0; j < nbrOfCe; j++) {
+ objects.add(loadConfigurationElementAndChildren(mainInput, extraInput, 1, 2, objects, -1), holdObjects);
+ }
+ }
+ return xpt;
+ } catch (IOException e) {
+ String message = NLS.bind(RegistryMessages.meta_regCacheIOExceptionReading, mainDataFile);
+ log(new Status(IStatus.ERROR, RegistryMessages.OWNER_NAME, fileError, message, e));
+ if (DEBUG)
+ log(new Status(IStatus.ERROR, RegistryMessages.OWNER_NAME, fileError, "Error reading an extension point tree (" + offset + ") from the registry cache", e)); //$NON-NLS-1$//$NON-NLS-2$
+ return null;
+ } finally {
+ closeExtraFile();
+ closeInputFile();
+ }
+ }
+
+ private Object loadExtensionPoint(int offset) {
+ try {
+ goToInputFile(offset);
+ return basicLoadExtensionPoint();
+ } catch (IOException e) {
+ String message = NLS.bind(RegistryMessages.meta_regCacheIOExceptionReading, mainDataFile);
+ log(new Status(IStatus.ERROR, RegistryMessages.OWNER_NAME, fileError, message, e));
+ if (DEBUG)
+ log(new Status(IStatus.ERROR, RegistryMessages.OWNER_NAME, fileError, "Error reading an extension point (" + offset + ") from the registry cache", e)); //$NON-NLS-1$ //$NON-NLS-2$
+ return null;
+ }
+ }
+
+ private ExtensionPoint basicLoadExtensionPoint() throws IOException {
+ int self = mainInput.readInt();
+ int[] children = readArray(mainInput);
+ int extraData = mainInput.readInt();
+ return getObjectFactory().createExtensionPoint(self, children, extraData);
+ }
+
+ private int[] readArray(DataInputStream in) throws IOException {
+ int arraySize = in.readInt();
+ if (arraySize == 0)
+ return RegistryObjectManager.EMPTY_INT_ARRAY;
+ int[] result = new int[arraySize];
+ for (int i = 0; i < arraySize; i++) {
+ result[i] = in.readInt();
+ }
+ return result;
+ }
+
+ private void goToInputFile(int offset) throws IOException {
+ mainInput.skipBytes(offset);
+ }
+
+ private void goToExtraFile(int offset) throws IOException {
+ extraInput.skipBytes(offset);
+ }
+
+ private String readStringOrNull(DataInputStream in, boolean intern) throws IOException {
+ byte type = in.readByte();
+ if (type == NULL)
+ return null;
+ if (intern)
+ return in.readUTF().intern();
+ return in.readUTF();
+ }
+
+ public String[] loadExtensionExtraData(int dataPosition) {
+ try {
+ goToExtraFile(dataPosition);
+ return basicLoadExtensionExtraData();
+ } catch (IOException e) {
+ String message = NLS.bind(RegistryMessages.meta_regCacheIOExceptionReading, extraDataFile);
+ log(new Status(IStatus.ERROR, RegistryMessages.OWNER_NAME, fileError, message, e));
+ if (DEBUG)
+ log(new Status(IStatus.ERROR, RegistryMessages.OWNER_NAME, fileError, "Error reading extension label (" + dataPosition + ") from the registry cache", e)); //$NON-NLS-1$ //$NON-NLS-2$
+ return null;
+ } finally {
+ closeExtraFile();
+ closeInputFile();
+ }
+ }
+
+ private String[] basicLoadExtensionExtraData() throws IOException {
+ return new String[] {readStringOrNull(extraInput, false), readStringOrNull(extraInput, false)};
+ }
+
+ public String[] loadExtensionPointExtraData(int offset) {
+ try {
+ goToExtraFile(offset);
+ return basicLoadExtensionPointExtraData();
+ } catch (IOException e) {
+ String message = NLS.bind(RegistryMessages.meta_regCacheIOExceptionReading, extraDataFile);
+ log(new Status(IStatus.ERROR, RegistryMessages.OWNER_NAME, fileError, message, e));
+ if (DEBUG)
+ log(new Status(IStatus.ERROR, RegistryMessages.OWNER_NAME, fileError, "Error reading extension point data (" + offset + ") from the resgistry cache", e)); //$NON-NLS-1$ //$NON-NLS-2$
+ return null;
+ } finally {
+ closeExtraFile();
+ closeInputFile();
+ }
+ }
+
+ private String[] basicLoadExtensionPointExtraData() throws IOException {
+ String[] result = new String[5];
+ result[0] = readStringOrNull(extraInput, false); //the label
+ result[1] = readStringOrNull(extraInput, false); //the schema
+ result[2] = readStringOrNull(extraInput, false); //the fully qualified name
+ result[3] = readStringOrNull(extraInput, false); //the namespace
+ result[4] = Long.toString(extraInput.readLong());
+ return result;
+ }
+
+ public KeyedHashSet loadNamespaces() {
+ DataInputStream namespaceInput = null;
+ try {
+ namespaceInput = new DataInputStream(new BufferedInputStream(new FileInputStream(contributionsFile)));
+ int size = namespaceInput.readInt();
+ KeyedHashSet result = new KeyedHashSet(size);
+ for (int i = 0; i < size; i++) {
+ Contribution n = getObjectFactory().createContribution(namespaceInput.readLong());
+ n.setRawChildren(readArray(namespaceInput));
+ result.add(n);
+ }
+ return result;
+ } catch (IOException e) {
+ String message = NLS.bind(RegistryMessages.meta_regCacheIOExceptionReading, contributionsFile);
+ log(new Status(IStatus.ERROR, RegistryMessages.OWNER_NAME, fileError, message, e));
+ return null;
+ } finally {
+ if (namespaceInput != null)
+ try {
+ namespaceInput.close();
+ } catch (IOException e1) {
+ //Ignore
+ }
+ }
+ }
+
+ private void loadAllOrphans(RegistryObjectManager objectManager) throws IOException {
+ //Read the extensions and configuration elements of the orphans
+ int orphans = objectManager.getOrphanExtensions().size();
+ for (int k = 0; k < orphans; k++) {
+ int numberOfOrphanExtensions = mainInput.readInt();
+ for (int i = 0; i < numberOfOrphanExtensions; i++) {
+ loadFullExtension(objectManager);
+ }
+ for (int i = 0; i < numberOfOrphanExtensions; i++) {
+ int nbrOfCe = mainInput.readInt();
+ for (int j = 0; j < nbrOfCe; j++) {
+ objectManager.add(loadConfigurationElementAndChildren(mainInput, extraInput, 1, Integer.MAX_VALUE, objectManager, -1), true);
+ }
+ }
+ }
+ }
+
+ public boolean readAllCache(RegistryObjectManager objectManager) {
+ try {
+ int size = objectManager.getExtensionPoints().size();
+ for (int i = 0; i < size; i++) {
+ objectManager.add(readAllExtensionPointTree(objectManager), holdObjects);
+ }
+ loadAllOrphans(objectManager);
+ } catch (IOException e) {
+ String message = NLS.bind(RegistryMessages.meta_regCacheIOExceptionReading, mainDataFile);
+ log(new Status(IStatus.ERROR, RegistryMessages.OWNER_NAME, fileError, message, e));
+ return false;
+ } finally {
+ closeExtraFile();
+ closeInputFile();
+ }
+ return true;
+ }
+
+ public ExtensionPoint readAllExtensionPointTree(RegistryObjectManager objectManager) throws IOException {
+ ExtensionPoint xpt = loadFullExtensionPoint();
+ int[] children = xpt.getRawChildren();
+ int nbrOfExtension = children.length;
+ for (int i = 0; i < nbrOfExtension; i++) {
+ loadFullExtension(objectManager);
+ }
+
+ for (int i = 0; i < nbrOfExtension; i++) {
+ int nbrOfCe = mainInput.readInt();
+ for (int j = 0; j < nbrOfCe; j++) {
+ objectManager.add(loadConfigurationElementAndChildren(mainInput, extraInput, 1, Integer.MAX_VALUE, objectManager, -1), true);
+ }
+ }
+ return xpt;
+ }
+
+ private ExtensionPoint loadFullExtensionPoint() throws IOException { //TODO I don't like this.
+ ExtensionPoint xpt = basicLoadExtensionPoint();
+ String[] tmp = basicLoadExtensionPointExtraData();
+ xpt.setLabel(tmp[0]);
+ xpt.setSchema(tmp[1]);
+ xpt.setUniqueIdentifier(tmp[2]);
+ xpt.setNamespace(tmp[3]);
+ xpt.setNamespaceOwnerId(Long.parseLong(tmp[4]));
+ return xpt;
+ }
+
+ private Extension loadFullExtension(RegistryObjectManager objectManager) throws IOException {
+ String[] tmp;
+ Extension loaded = basicLoadExtension(mainInput);
+ tmp = basicLoadExtensionExtraData();
+ loaded.setLabel(tmp[0]);
+ loaded.setExtensionPointIdentifier(tmp[1]);
+ objectManager.add(loaded, holdObjects);
+ return loaded;
+ }
+
+ public HashMap loadOrphans() {
+ DataInputStream orphanInput = null;
+ try {
+ orphanInput = new DataInputStream(new BufferedInputStream(new FileInputStream(orphansFile)));
+ int size = orphanInput.readInt();
+ HashMap result = new HashMap(size);
+ for (int i = 0; i < size; i++) {
+ String key = orphanInput.readUTF();
+ int[] value = readArray(orphanInput);
+ result.put(key, value);
+ }
+ return result;
+ } catch (IOException e) {
+ return null;
+ } finally {
+ if (orphanInput != null)
+ try {
+ orphanInput.close();
+ } catch (IOException e1) {
+ //ignore
+ }
+ }
+ }
+
+ public void setHoldObjects(boolean holdObjects) {
+ this.holdObjects = holdObjects;
+ }
+
+ private void log(Status status) {
+ registry.log(status);
+ }
+
+ private RegistryObjectFactory getObjectFactory() {
+ return registry.getElementFactory();
+ }
+
+ // Returns a file name used to test if cache is actually present at a given location
+ public static String getTestFileName() {
+ return TABLE;
+ }
+
+}
diff --git a/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/TableWriter.java b/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/TableWriter.java
new file mode 100644
index 000000000..2139bec3f
--- /dev/null
+++ b/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/TableWriter.java
@@ -0,0 +1,314 @@
+/*******************************************************************************
+ * 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.registry;
+
+import java.io.*;
+import java.util.*;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.equinox.registry.IConfigurationElement;
+import org.eclipse.equinox.registry.IExtension;
+
+public class TableWriter {
+ private static final byte fileError = 0;
+
+ File mainDataFile;
+ File extraDataFile;
+ File tableFile;
+ File contributionsFile;
+ File orphansFile;
+
+ void setMainDataFile(File main) {
+ mainDataFile = main;
+ }
+
+ void setExtraDataFile(File extra) {
+ extraDataFile = extra;
+ }
+
+ void setTableFile(File table) {
+ tableFile = table;
+ }
+
+ void setContributionsFile(File fileName) {
+ contributionsFile = fileName;
+ }
+
+ void setOrphansFile(File orphan) {
+ orphansFile = orphan;
+ }
+
+ DataOutputStream mainOutput;
+ DataOutputStream extraOutput;
+ FileOutputStream mainFileOutput = null;
+ FileOutputStream extraFileOutput = null;
+
+ private HashtableOfInt offsets;
+
+ private ExtensionRegistry registry;
+
+ public TableWriter(ExtensionRegistry registry) {
+ this.registry = registry;
+ }
+
+ private int getExtraDataPosition() {
+ return extraOutput.size();
+ }
+
+ public boolean saveCache(RegistryObjectManager objectManager, long timestamp) {
+ try {
+ if (!openFiles())
+ return false;
+ try {
+ saveExtensionRegistry(objectManager, timestamp);
+ } catch (IOException io) {
+ log(new Status(IStatus.ERROR, RegistryMessages.OWNER_NAME, fileError, RegistryMessages.meta_registryCacheWriteProblems, io));
+ return false;
+ }
+ } finally {
+ closeFiles();
+ }
+ return true;
+ }
+
+ private boolean openFiles() {
+ try {
+ mainFileOutput = new FileOutputStream(mainDataFile);
+ mainOutput = new DataOutputStream(new BufferedOutputStream(mainFileOutput));
+ extraFileOutput = new FileOutputStream(extraDataFile);
+ extraOutput = new DataOutputStream(new BufferedOutputStream(extraFileOutput));
+ } catch (FileNotFoundException e) {
+ if (mainFileOutput != null)
+ try {
+ mainFileOutput.close();
+ } catch (IOException e1) {
+ //Ignore
+ }
+
+ log(new Status(IStatus.ERROR, RegistryMessages.OWNER_NAME, fileError, RegistryMessages.meta_unableToCreateCache, e));
+ return false;
+ }
+ return true;
+ }
+
+ private void closeFiles() {
+ try {
+ if (mainOutput != null) {
+ mainOutput.flush();
+ if (mainFileOutput.getFD().valid()) {
+ mainFileOutput.getFD().sync();
+ }
+ mainOutput.close();
+ }
+ } catch (IOException e) {
+ log(new Status(IStatus.ERROR, RegistryMessages.OWNER_NAME, fileError, RegistryMessages.meta_registryCacheWriteProblems, e));
+ e.printStackTrace();
+ }
+ try {
+ if (extraOutput != null) {
+ extraOutput.flush();
+ if (extraFileOutput.getFD().valid()) {
+ extraFileOutput.getFD().sync();
+ }
+ extraOutput.close();
+ }
+ } catch (IOException e) {
+ log(new Status(IStatus.ERROR, RegistryMessages.OWNER_NAME, fileError, RegistryMessages.meta_registryCacheWriteProblems, e));
+ e.printStackTrace();
+ }
+ }
+
+ private void saveExtensionRegistry(RegistryObjectManager objectManager, long timestamp) throws IOException {
+ ExtensionPointHandle[] points = objectManager.getExtensionPointsHandles();
+ offsets = new HashtableOfInt(objectManager.getNextId());
+ for (int i = 0; i < points.length; i++) {
+ saveExtensionPoint(points[i]);
+ }
+ saveOrphans(objectManager);
+ saveNamespaces(objectManager.getContributions());
+ closeFiles(); //Close the files here so we can write the appropriate size information in the table file.
+ saveTables(objectManager, timestamp); //Write the table last so if that is something went wrong we can know
+ }
+
+ private void saveNamespaces(KeyedHashSet[] contributions) throws IOException {
+ FileOutputStream fosNamespace = new FileOutputStream(contributionsFile);
+ DataOutputStream outputNamespace = new DataOutputStream(new BufferedOutputStream(fosNamespace));
+ KeyedElement[] newElements = contributions[0].elements();
+ KeyedElement[] formerElements = contributions[1].elements();
+ outputNamespace.writeInt(newElements.length + formerElements.length);
+ for (int i = 0; i < newElements.length; i++) {
+ Contribution elt = (Contribution) newElements[i];
+ outputNamespace.writeLong(elt.getContributorId());
+ saveArray(elt.getRawChildren(), outputNamespace);
+ }
+ for (int i = 0; i < formerElements.length; i++) {
+ Contribution elt = (Contribution) formerElements[i];
+ outputNamespace.writeLong(elt.getContributorId());
+ saveArray(elt.getRawChildren(), outputNamespace);
+ }
+ outputNamespace.flush();
+ fosNamespace.getFD().sync();
+ outputNamespace.close();
+ }
+
+ private void saveTables(RegistryObjectManager objectManager, long registryTimeStamp) throws IOException {
+ FileOutputStream fosTable = new FileOutputStream(tableFile);
+ DataOutputStream outputTable = new DataOutputStream(new BufferedOutputStream(fosTable));
+ writeCacheHeader(outputTable, registryTimeStamp);
+ outputTable.writeInt(objectManager.getNextId());
+ offsets.save(outputTable);
+ objectManager.getExtensionPoints().save(outputTable);
+ outputTable.flush();
+ fosTable.getFD().sync();
+ outputTable.close();
+ }
+
+ private void writeCacheHeader(DataOutputStream output, long registryTimeStamp) throws IOException {
+ output.writeInt(TableReader.CACHE_VERSION);
+ output.writeLong(registry.computeState());
+ output.writeLong(registryTimeStamp);
+ output.writeLong(mainDataFile.length());
+ output.writeLong(extraDataFile.length());
+ output.writeLong(contributionsFile.length());
+ output.writeLong(orphansFile.length());
+ output.writeUTF(System.getProperty(IRegistryConstants.PROP_OS));
+ output.writeUTF(System.getProperty(IRegistryConstants.PROP_WS));
+ output.writeUTF(System.getProperty(IRegistryConstants.PROP_NL));
+ }
+
+ private void saveArray(int[] array, DataOutputStream out) throws IOException {
+ if (array == null) {
+ out.writeInt(0);
+ return;
+ }
+ out.writeInt(array.length);
+ for (int i = 0; i < array.length; i++) {
+ out.writeInt(array[i]);
+ }
+ }
+
+ private void saveExtensionPoint(ExtensionPointHandle xpt) throws IOException {
+ //save the file position
+ offsets.put(xpt.getId(), mainOutput.size());
+ //save the extensionPoint
+ mainOutput.writeInt(xpt.getId());
+ saveArray(xpt.getObject().getRawChildren(), mainOutput);
+ mainOutput.writeInt(getExtraDataPosition());
+ saveExtensionPointData(xpt);
+
+ saveExtensions(xpt.getExtensions(), mainOutput);
+ }
+
+ private void saveExtension(ExtensionHandle ext, DataOutputStream outputStream) throws IOException {
+ offsets.put(ext.getId(), outputStream.size());
+ outputStream.writeInt(ext.getId());
+ writeStringOrNull(ext.getSimpleIdentifier(), outputStream);
+ writeStringOrNull(ext.getNamespace(), outputStream);
+ saveArray(ext.getObject().getRawChildren(), outputStream);
+ outputStream.writeInt(getExtraDataPosition());
+ saveExtensionData(ext);
+ }
+
+ private void writeStringArray(String[] array, DataOutputStream outputStream) throws IOException {
+ outputStream.writeInt(array == null ? 0 : array.length);
+ for (int i = 0; i < (array == null ? 0 : array.length); i++) {
+ writeStringOrNull(array[i], outputStream);
+ }
+ }
+
+ //Save Configuration elements depth first
+ private void saveConfigurationElement(ConfigurationElementHandle element, DataOutputStream outputStream, DataOutputStream extraOutputStream, int depth) throws IOException {
+ DataOutputStream currentOutput = outputStream;
+ if (depth > 2)
+ currentOutput = extraOutputStream;
+
+ offsets.put(element.getId(), currentOutput.size());
+
+ currentOutput.writeInt(element.getId());
+ ConfigurationElement actualCe = (ConfigurationElement) element.getObject();
+
+ currentOutput.writeLong(actualCe.getNamespaceOwnerId());
+ writeStringOrNull(actualCe.getName(), currentOutput);
+ currentOutput.writeInt(actualCe.parentId);
+ currentOutput.writeByte(actualCe.parentType);
+ currentOutput.writeInt(depth > 1 ? extraOutputStream.size() : -1);
+ writeStringArray(actualCe.getPropertiesAndValue(), currentOutput);
+ //save the children
+ saveArray(actualCe.getRawChildren(), currentOutput);
+
+ ConfigurationElementHandle[] childrenCEs = (ConfigurationElementHandle[]) element.getChildren();
+ for (int i = 0; i < childrenCEs.length; i++) {
+ saveConfigurationElement(childrenCEs[i], outputStream, extraOutputStream, depth + 1);
+ }
+
+ }
+
+ private void saveExtensions(IExtension[] exts, DataOutputStream outputStream) throws IOException {
+ for (int i = 0; i < exts.length; i++) {
+ saveExtension((ExtensionHandle) exts[i], outputStream);
+ }
+
+ for (int i = 0; i < exts.length; i++) {
+ IConfigurationElement[] ces = exts[i].getConfigurationElements();
+ outputStream.writeInt(ces.length);
+ for (int j = 0; j < ces.length; j++) {
+ saveConfigurationElement((ConfigurationElementHandle) ces[j], outputStream, extraOutput, 1);
+ }
+ }
+ }
+
+ private void saveExtensionPointData(ExtensionPointHandle xpt) throws IOException {
+ writeStringOrNull(xpt.getLabel(), extraOutput);
+ writeStringOrNull(xpt.getSchemaReference(), extraOutput);
+ writeStringOrNull(xpt.getUniqueIdentifier(), extraOutput);
+ writeStringOrNull(xpt.getNamespace(), extraOutput);
+ extraOutput.writeLong(((ExtensionPoint) xpt.getObject()).getNamespaceOwnerId());
+ }
+
+ private void saveExtensionData(ExtensionHandle extension) throws IOException {
+ writeStringOrNull(extension.getLabel(), extraOutput);
+ writeStringOrNull(extension.getExtensionPointUniqueIdentifier(), extraOutput);
+ }
+
+ private void writeStringOrNull(String string, DataOutputStream out) throws IOException {
+ if (string == null)
+ out.writeByte(TableReader.NULL);
+ else {
+ out.writeByte(TableReader.OBJECT);
+ out.writeUTF(string);
+ }
+ }
+
+ private void saveOrphans(RegistryObjectManager objectManager) throws IOException {
+ Map orphans = objectManager.getOrphanExtensions();
+ FileOutputStream fosOrphan = new FileOutputStream(orphansFile);
+ DataOutputStream outputOrphan = new DataOutputStream(new BufferedOutputStream(fosOrphan));
+ outputOrphan.writeInt(orphans.size());
+ Set elements = orphans.entrySet();
+ for (Iterator iter = elements.iterator(); iter.hasNext();) {
+ Map.Entry entry = (Map.Entry) iter.next();
+ outputOrphan.writeUTF((String) entry.getKey());
+ saveArray((int[]) entry.getValue(), outputOrphan);
+ }
+ for (Iterator iter = elements.iterator(); iter.hasNext();) {
+ Map.Entry entry = (Map.Entry) iter.next();
+ mainOutput.writeInt(((int[]) entry.getValue()).length);
+ saveExtensions((IExtension[]) objectManager.getHandles((int[]) entry.getValue(), RegistryObjectManager.EXTENSION), mainOutput);
+ }
+ outputOrphan.flush();
+ fosOrphan.getFD().sync();
+ outputOrphan.close();
+ }
+
+ private void log(Status status) {
+ registry.log(status);
+ }
+}
diff --git a/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/TemporaryObjectManager.java b/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/TemporaryObjectManager.java
new file mode 100644
index 000000000..bffa67818
--- /dev/null
+++ b/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/TemporaryObjectManager.java
@@ -0,0 +1,137 @@
+/*******************************************************************************
+ * 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.registry;
+
+import java.util.Map;
+import org.eclipse.core.runtime.InvalidRegistryObjectException;
+
+/**
+ * @since 3.1
+ */
+public class TemporaryObjectManager implements IObjectManager {
+ private Map actualObjects; //id --> registry objects
+ private RegistryObjectManager parent; //the main object manager (should be equals to extensionRegistry.getObjectManager)
+
+ public TemporaryObjectManager(Map actualObjects, RegistryObjectManager parent) {
+ this.actualObjects = actualObjects;
+ this.parent = parent;
+ }
+
+ public Handle getHandle(int id, byte type) {
+ switch (type) {
+ case RegistryObjectManager.EXTENSION_POINT :
+ return new ExtensionPointHandle(this, id);
+
+ case RegistryObjectManager.EXTENSION :
+ return new ExtensionHandle(this, id);
+
+ case RegistryObjectManager.CONFIGURATION_ELEMENT :
+ return new ConfigurationElementHandle(this, id);
+
+ case RegistryObjectManager.THIRDLEVEL_CONFIGURATION_ELEMENT :
+ default : //avoid compiler error, type should always be known
+ return new ThirdLevelConfigurationElementHandle(this, id);
+ }
+ }
+
+ public Handle[] getHandles(int[] ids, byte type) {
+ Handle[] results = null;
+ int nbrId = ids.length;
+ switch (type) {
+ case RegistryObjectManager.EXTENSION_POINT :
+ if (nbrId == 0)
+ return ExtensionPointHandle.EMPTY_ARRAY;
+ results = new ExtensionPointHandle[nbrId];
+ for (int i = 0; i < nbrId; i++) {
+ results[i] = new ExtensionPointHandle(this, ids[i]);
+ }
+ break;
+
+ case RegistryObjectManager.EXTENSION :
+ if (nbrId == 0)
+ return ExtensionHandle.EMPTY_ARRAY;
+ results = new ExtensionHandle[nbrId];
+ for (int i = 0; i < nbrId; i++) {
+ results[i] = new ExtensionHandle(this, ids[i]);
+ }
+ break;
+
+ case RegistryObjectManager.CONFIGURATION_ELEMENT :
+ if (nbrId == 0)
+ return ConfigurationElementHandle.EMPTY_ARRAY;
+ results = new ConfigurationElementHandle[nbrId];
+ for (int i = 0; i < nbrId; i++) {
+ results[i] = new ConfigurationElementHandle(this, ids[i]);
+ }
+ break;
+
+ case RegistryObjectManager.THIRDLEVEL_CONFIGURATION_ELEMENT :
+ if (nbrId == 0)
+ return ConfigurationElementHandle.EMPTY_ARRAY;
+ results = new ThirdLevelConfigurationElementHandle[nbrId];
+ for (int i = 0; i < nbrId; i++) {
+ results[i] = new ThirdLevelConfigurationElementHandle(this, ids[i]);
+ }
+ break;
+ }
+ return results;
+ }
+
+ synchronized public Object getObject(int id, byte type) {
+ Object result = null;
+ try {
+ result = parent.getObject(id, type);
+ } catch (InvalidRegistryObjectException e) {
+ if (actualObjects != null) {
+ result = actualObjects.get(new Integer(id));
+ }
+ }
+ if (result == null)
+ throw new InvalidRegistryObjectException();
+ return result;
+ }
+
+ synchronized public RegistryObject[] getObjects(int[] values, byte type) {
+ if (values.length == 0) {
+ switch (type) {
+ case RegistryObjectManager.EXTENSION_POINT :
+ return ExtensionPoint.EMPTY_ARRAY;
+ case RegistryObjectManager.EXTENSION :
+ return Extension.EMPTY_ARRAY;
+ case RegistryObjectManager.CONFIGURATION_ELEMENT :
+ case RegistryObjectManager.THIRDLEVEL_CONFIGURATION_ELEMENT :
+ return ConfigurationElement.EMPTY_ARRAY;
+ }
+ }
+
+ RegistryObject[] results = null;
+ switch (type) {
+ case RegistryObjectManager.EXTENSION_POINT :
+ results = new ExtensionPoint[values.length];
+ break;
+ case RegistryObjectManager.EXTENSION :
+ results = new Extension[values.length];
+ break;
+ case RegistryObjectManager.CONFIGURATION_ELEMENT :
+ case RegistryObjectManager.THIRDLEVEL_CONFIGURATION_ELEMENT :
+ results = new ConfigurationElement[values.length];
+ break;
+ }
+ for (int i = 0; i < values.length; i++) {
+ results[i] = (RegistryObject) getObject(values[i], type);
+ }
+ return results;
+ }
+
+ public synchronized void close() {
+ actualObjects = null;
+ }
+}
diff --git a/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/ThirdLevelConfigurationElementHandle.java b/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/ThirdLevelConfigurationElementHandle.java
new file mode 100644
index 000000000..1931b8c88
--- /dev/null
+++ b/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/ThirdLevelConfigurationElementHandle.java
@@ -0,0 +1,32 @@
+/*******************************************************************************
+ * 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.registry;
+
+import org.eclipse.equinox.registry.IConfigurationElement;
+
+/**
+ * @since 3.1
+ */
+public class ThirdLevelConfigurationElementHandle extends ConfigurationElementHandle {
+
+ public ThirdLevelConfigurationElementHandle(IObjectManager objectManager, int id) {
+ super(objectManager, id);
+ }
+
+ protected ConfigurationElement getConfigurationElement() {
+ return (ConfigurationElement) objectManager.getObject(getId(), RegistryObjectManager.THIRDLEVEL_CONFIGURATION_ELEMENT);
+ }
+
+ public IConfigurationElement[] getChildren() {
+ return (IConfigurationElement[]) objectManager.getHandles(getConfigurationElement().getRawChildren(), RegistryObjectManager.THIRDLEVEL_CONFIGURATION_ELEMENT);
+ }
+
+}
diff --git a/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/messages.properties b/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/messages.properties
new file mode 100644
index 000000000..b0cf8bd68
--- /dev/null
+++ b/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/messages.properties
@@ -0,0 +1,57 @@
+###############################################################################
+# 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
+###############################################################################
+### Extension registry messages
+
+### Extension Registry
+meta_registryCacheWriteProblems = Trouble writing to the registry cache file.
+meta_registryCacheReadProblems = Trouble reading from the registry cache file.
+meta_regCacheIOExceptionReading = IOException encountered while reading \"{0}\".
+meta_registryCacheInconsistent = Registry cache inconsistent, defaulting to not using cached file.
+meta_unableToCreateCache = Unable to create output stream for registry cache.
+meta_unableToReadCache = Unable to create input stream for registry cache.
+registry_no_default = Extension tracker was unable to obtain Eclipse extension registry.
+
+### parsing/resolve
+parse_error = Parsing error: \"{0}\".
+parse_errorNameLineColumn = Parsing error in \"{0}\" [line {1}, column {2}]: \"{3}\".
+parse_internalStack = Element/end element mismatch for element \"{0}\".
+parse_missingAttribute=Missing \"{0}\" attribute in \"{1}\" element. Element ignored.
+parse_missingAttributeLine=Missing \"{0}\" attribute in \"{1}\" element (line: {2}). Element ignored.
+parse_unknownAttribute = Unknown attribute \"{1}\" for element \"{0}\" ignored.
+parse_unknownAttributeLine= Unknown attribute \"{1}\" for element \"{0}\" ignored (line: {2}).
+parse_unknownElement = Unknown element \"{1}\", found within a \"{0}\", ignored.
+parse_unknownElementLine = Unknown element \"{1}\", found within a \"{0}\", ignored (line: {2}).
+parse_unknownTopElement = Unknown element \"{0}\", found at the top level, ignored.
+parse_xmlParserNotAvailable=Could not acquire XML parsing service.
+parse_process = Processing XML extension registry contribution
+parse_failedParsingManifest = Could not parse XML contribution for \"{0}\". Any contributed extensions and extension points will be ignored.
+parse_nonSingleton = The extensions and extension-points from the bundle \"{0}\" are ignored. The bundle is not marked as singleton.
+parse_problems = Problems parsing plug-in manifest for: \"{0}\".
+
+### direct creation
+create_failedExtensionPoint = Missing ID for the extension point \"{0}\". Element ignored.
+
+### executable extensions
+exExt_findClassError = Contributor \"{0}\" was unable to find class \"{1}\".
+exExt_instantiateClassError = Contributor \"{0}\" was unable to instantiate class \"{1}\".
+exExt_initObjectError = Contributor \"{0}\" was unable to execute setInitializationData on an instance of \"{1}\".
+exExt_extDefNotFound = Executable extension definition for \"{0}\" not found.
+
+### plugins
+plugin_eventListenerError = Error notifying registry change listener.
+plugin_initObjectError = Plug-in \"{0}\" was unable to execute setInitializationData on an instance of \"{1}\".
+plugin_instantiateClassError = Plug-in \"{0}\" was unable to instantiate class \"{1}\".
+plugin_loadClassError = Plug-in {0} was unable to load class {1}.
+
+### logging
+log_error = Error:
+log_warning = Warning:
+log_log = Log:
diff --git a/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/osgi/Activator.java b/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/osgi/Activator.java
new file mode 100644
index 000000000..7932ce39b
--- /dev/null
+++ b/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/osgi/Activator.java
@@ -0,0 +1,71 @@
+/*******************************************************************************
+ * 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.registry.osgi;
+
+import org.eclipse.core.internal.registry.IRegistryConstants;
+import org.eclipse.osgi.service.environment.EnvironmentInfo;
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.util.tracker.ServiceTracker;
+
+/**
+ * The extension registry bundle manager class.
+ */
+public class Activator implements BundleActivator {
+
+ private static BundleContext bundleContext;
+
+ /**
+ * This method is called upon plug-in activation
+ */
+ public void start(BundleContext context) throws Exception {
+ bundleContext = context;
+ processCommandLine();
+ }
+
+ /**
+ * This method is called when the plug-in is stopped
+ */
+ public void stop(BundleContext context) throws Exception {
+ bundleContext = null;
+ }
+
+ public static BundleContext getContext() {
+ return bundleContext;
+ }
+
+ /**
+ * Look for the no registry cache flag and check to see if we should NOT be lazily loading plug-in
+ * definitions from the registry cache file.
+ * NOTE: this command line processing is only performed in the presence of OSGi
+ *
+ * @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(IRegistryConstants.NO_REGISTRY_CACHE))
+ System.getProperties().setProperty(IRegistryConstants.PROP_NO_REGISTRY_CACHE, "true"); //$NON-NLS-1$
+ else if (args[i].equalsIgnoreCase(IRegistryConstants.NO_LAZY_REGISTRY_CACHE_LOADING))
+ System.getProperties().setProperty(IRegistryConstants.PROP_NO_LAZY_CACHE_LOADING, "true"); //$NON-NLS-1$
+ }
+ }
+
+}
diff --git a/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/osgi/EclipseBundleListener.java b/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/osgi/EclipseBundleListener.java
new file mode 100644
index 000000000..e907394ed
--- /dev/null
+++ b/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/osgi/EclipseBundleListener.java
@@ -0,0 +1,184 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 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.registry.osgi;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.*;
+import org.eclipse.core.internal.registry.*;
+import org.eclipse.core.internal.runtime.ResourceTranslator;
+import org.eclipse.core.internal.runtime.RuntimeLog;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.osgi.util.ManifestElement;
+import org.eclipse.osgi.util.NLS;
+import org.osgi.framework.*;
+
+/**
+ * A listener for bundle events. When a bundles come and go we look to see
+ * if there are any extensions or extension points and update the registry accordingly.
+ * Using a Synchronous listener here is important. If the
+ * bundle activator code tries to access the registry to get its extension
+ * points, we need to ensure that they are in the registry before the
+ * bundle start is called. By listening sync we are able to ensure that
+ * happens.
+ */
+public class EclipseBundleListener implements SynchronousBundleListener {
+ private static final String PLUGIN_MANIFEST = "plugin.xml"; //$NON-NLS-1$
+ private static final String FRAGMENT_MANIFEST = "fragment.xml"; //$NON-NLS-1$
+
+ private ExtensionRegistry registry;
+ private Object token;
+
+ public EclipseBundleListener(ExtensionRegistry registry, Object key) {
+ this.registry = registry;
+ this.token = key;
+ }
+
+ public void bundleChanged(BundleEvent event) {
+ /* Only should listen for RESOLVED and UNRESOLVED events.
+ *
+ * When a bundle is updated the Framework will publish an UNRESOLVED and
+ * then a RESOLVED event which should cause the bundle to be removed
+ * and then added back into the registry.
+ *
+ * When a bundle is uninstalled the Framework should publish an UNRESOLVED
+ * event and then an UNINSTALLED event so the bundle will have been removed
+ * by the UNRESOLVED event before the UNINSTALLED event is published.
+ *
+ * When a bundle is refreshed from PackageAdmin an UNRESOLVED event will be
+ * published which will remove the bundle from the registry. If the bundle
+ * can be RESOLVED after a refresh then a RESOLVED event will be published
+ * which will add the bundle back. This is required because the classloader
+ * will have been refreshed for the bundle so all extensions and extension
+ * points for the bundle must be refreshed.
+ */
+ Bundle bundle = event.getBundle();
+ switch (event.getType()) {
+ case BundleEvent.RESOLVED :
+ addBundle(bundle);
+ break;
+ case BundleEvent.UNRESOLVED :
+ removeBundle(bundle);
+ break;
+ }
+ }
+
+ public void processBundles(Bundle[] bundles) {
+ for (int i = 0; i < bundles.length; i++) {
+ if (isBundleResolved(bundles[i]))
+ addBundle(bundles[i]);
+ else
+ removeBundle(bundles[i]);
+ }
+ }
+
+ private boolean isBundleResolved(Bundle bundle) {
+ return (bundle.getState() & (Bundle.RESOLVED | Bundle.ACTIVE | Bundle.STARTING | Bundle.STOPPING)) != 0;
+ }
+
+ private void removeBundle(Bundle bundle) {
+ registry.remove(bundle.getBundleId());
+ }
+
+ private void addBundle(Bundle bundle) {
+ // if the given bundle already exists in the registry then return.
+ // note that this does not work for update cases.
+ if (registry.hasNamespace(bundle.getBundleId()))
+ return;
+ // bail out if system bundle
+ if (bundle.getBundleId() == 0)
+ return;
+ // bail out if the bundle does not have a symbolic name
+ if (bundle.getSymbolicName() == null)
+ return;
+ //If the bundle is not a singleton, then it is not added
+ if (!isSingleton(bundle))
+ return;
+
+ boolean isFragment = OSGIUtils.getDefault().isFragment(bundle);
+
+ //If the bundle is a fragment being added to a non singleton host, then it is not added
+ if (isFragment) {
+ Bundle[] hosts = OSGIUtils.getDefault().getHosts(bundle);
+ if (hosts != null && isSingleton(hosts[0]) == false)
+ return;
+ }
+
+ InputStream is = null;
+ String manifestType = null;
+ String manifestName = isFragment ? FRAGMENT_MANIFEST : PLUGIN_MANIFEST;
+ try {
+ URL url = bundle.getEntry(manifestName);
+ if (url != null) {
+ is = url.openStream();
+ manifestType = isFragment ? ExtensionsParser.FRAGMENT : ExtensionsParser.PLUGIN;
+ }
+ } catch (IOException ex) {
+ is = null;
+ }
+ if (is == null)
+ return;
+
+ long contributorId = bundle.getBundleId();
+ ResourceBundle b = null;
+ try {
+ b = ResourceTranslator.getResourceBundle(bundle);
+ } catch (MissingResourceException e) {
+ //Ignore the exception
+ }
+
+ registry.addXMLContribution(is, contributorId, manifestType, manifestName, b, token);
+
+ // bug 70941
+ // need to ensure we can find resource bundles from fragments
+ // The code below no longer seems necessary as all runtime plugins are installed
+ // before the corresponding Message classes are instantiated.
+ // if (RuntimeUtils.PI_RUNTIME.equals(bundleModel.getNamespace()))
+ // Messages.reloadMessages();
+ }
+
+ private boolean isSingleton(Bundle bundle) {
+ Dictionary allHeaders = bundle.getHeaders(""); //$NON-NLS-1$
+ String symbolicNameHeader = (String) allHeaders.get(Constants.BUNDLE_SYMBOLICNAME);
+ try {
+ if (symbolicNameHeader != null) {
+ ManifestElement[] symbolicNameElements = ManifestElement.parseHeader(Constants.BUNDLE_SYMBOLICNAME, symbolicNameHeader);
+ if (symbolicNameElements.length > 0) {
+ String singleton = symbolicNameElements[0].getDirective(Constants.SINGLETON_DIRECTIVE);
+ if (singleton == null)
+ singleton = symbolicNameElements[0].getAttribute(Constants.SINGLETON_DIRECTIVE);
+
+ if (!"true".equalsIgnoreCase(singleton)) { //$NON-NLS-1$
+ int status = IStatus.INFO;
+ String manifestVersion = (String) allHeaders.get(org.osgi.framework.Constants.BUNDLE_MANIFESTVERSION);
+ if (manifestVersion == null) {//the header was not defined for previous versions of the bundle
+ //3.0 bundles without a singleton attributes are still being accepted
+ if (OSGIUtils.getDefault().getBundle(symbolicNameElements[0].getValue()) == bundle) {
+ return true;
+ }
+ status = IStatus.ERROR;
+ }
+ if (registry.debug() || status == IStatus.ERROR) {
+ String message = NLS.bind(RegistryMessages.parse_nonSingleton, bundle.getLocation());
+ RuntimeLog.log(new Status(status, RegistryMessages.OWNER_NAME, 0, message, null));
+ }
+ return false;
+ }
+ }
+ }
+ } catch (BundleException e1) {
+ //This can't happen because the fwk would have rejected the bundle
+ }
+ return true;
+ }
+}
diff --git a/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/osgi/EquinoxRegistryStrategy.java b/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/osgi/EquinoxRegistryStrategy.java
new file mode 100644
index 000000000..5e53d59ba
--- /dev/null
+++ b/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/osgi/EquinoxRegistryStrategy.java
@@ -0,0 +1,76 @@
+/*******************************************************************************
+ * 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.registry.osgi;
+
+import java.io.File;
+import org.eclipse.core.internal.registry.IRegistryConstants;
+import org.eclipse.core.internal.runtime.RuntimeLog;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.osgi.service.datalocation.Location;
+import org.eclipse.osgi.service.resolver.PlatformAdmin;
+
+/**
+ * The registry strategy used by the Equinox extension registry. Adds to the OSGi registry:
+ * - Use debug information supplied via .options files
+ * - Use Eclipse logging
+ * - Use Eclipse platform state for cache validation
+ * - Supplied alternative cache location (primarily used with shared installs)
+ *
+ * @since org.eclipse.equinox.registry 1.0
+ */
+public class EquinoxRegistryStrategy extends RegistryStrategyOSGI {
+
+ public static final String PLUGIN_NAME = "org.eclipse.equinox.registry"; //$NON-NLS-1$
+ public static final String OPTION_DEBUG = PLUGIN_NAME + "/debug"; //$NON-NLS-1$
+ public static final String OPTION_DEBUG_EVENTS = PLUGIN_NAME + "/debug/events"; //$NON-NLS-1$
+
+ private static boolean DEBUG_ECLIPSE_REGISTRY = OSGIUtils.getDefault().getBooleanDebugOption(OPTION_DEBUG, false);
+ private static boolean DEBUG_ECLIPSE_EVENTS = OSGIUtils.getDefault().getBooleanDebugOption(OPTION_DEBUG_EVENTS, false);
+
+ public EquinoxRegistryStrategy(File theStorageDir, boolean cacheReadOnly, Object key) {
+ super(theStorageDir, cacheReadOnly, key);
+ }
+
+ public boolean debug() {
+ return DEBUG_ECLIPSE_REGISTRY;
+ }
+
+ public boolean debugRegistryEvents() {
+ return DEBUG_ECLIPSE_EVENTS;
+ }
+
+ public final void log(IStatus status) {
+ RuntimeLog.log(status);
+ }
+
+ public long cacheComputeState() {
+ PlatformAdmin admin = OSGIUtils.getDefault().getPlatformAdmin();
+ return admin == null ? -1 : admin.getState(false).getTimeStamp();
+ }
+
+ // Eclipse extension registry cache can be found in one of the two locations:
+ // a) in the local configuration area (standard location passed in by the platform)
+ // b) in the shared configuration area (typically, shared install is used)
+ public File cacheAlternativeLocation() {
+ // In debug be careful of LocationManager.computeSharedConfigurationLocation
+ // and PROP_SHARED_CONFIG_AREA = "osgi.sharedConfiguration.area" - it
+ // seems to work differently comparing to stand-alone execution.
+ Location currentLocation = OSGIUtils.getDefault().getConfigurationLocation();
+ if (currentLocation == null)
+ return null;
+ Location parentLocation = currentLocation.getParentLocation();
+ if (parentLocation == null)
+ return null;
+ String theRegistryLocation = parentLocation.getURL().getFile() + '/' + IRegistryConstants.RUNTIME_NAME;
+ return new File(theRegistryLocation);
+ }
+
+}
diff --git a/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/osgi/OSGIUtils.java b/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/osgi/OSGIUtils.java
new file mode 100644
index 000000000..ea9e8d993
--- /dev/null
+++ b/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/osgi/OSGIUtils.java
@@ -0,0 +1,187 @@
+/*******************************************************************************
+ * 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.registry.osgi;
+
+import java.util.Date;
+import org.eclipse.osgi.service.datalocation.Location;
+import org.eclipse.osgi.service.debug.DebugOptions;
+import org.eclipse.osgi.service.resolver.PlatformAdmin;
+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 closeServices() method should be called before the plugin is stopped.
+ *
+ * @since org.eclipse.equinox.registry 1.0
+ */
+public class OSGIUtils {
+ private ServiceTracker debugTracker = null;
+ private ServiceTracker bundleTracker = null;
+ private ServiceTracker platfromTracker = null;
+ private ServiceTracker configurationLocationTracker = 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 OSGIUtils singleton = new OSGIUtils();
+
+ public static OSGIUtils getDefault() {
+ return singleton;
+ }
+
+ /**
+ * Private constructor to block instance creation.
+ */
+ private OSGIUtils() {
+ 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("PreferencesUtils called before plugin started"); //$NON-NLS-1$
+ return;
+ }
+
+ debugTracker = new ServiceTracker(context, DebugOptions.class.getName(), null);
+ debugTracker.open();
+
+ bundleTracker = new ServiceTracker(context, PackageAdmin.class.getName(), null);
+ bundleTracker.open();
+
+ platfromTracker = new ServiceTracker(context, PlatformAdmin.class.getName(), null);
+ platfromTracker.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();
+
+ }
+
+ void closeServices() {
+ if (debugTracker != null) {
+ debugTracker.close();
+ debugTracker = null;
+ }
+ if (bundleTracker != null) {
+ bundleTracker.close();
+ bundleTracker = null;
+ }
+ if (configurationLocationTracker != null) {
+ configurationLocationTracker.close();
+ configurationLocationTracker = null;
+ }
+ if (platfromTracker != null) {
+ platfromTracker.close();
+ platfromTracker = null;
+ }
+ }
+
+ public boolean getBooleanDebugOption(String option, boolean defaultValue) {
+ if (debugTracker == null) {
+ 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 PackageAdmin getPackageAdmin() {
+ if (bundleTracker == null) {
+ message("Bundle tracker is not set"); //$NON-NLS-1$
+ return null;
+ }
+ return (PackageAdmin) bundleTracker.getService();
+ }
+
+ public Bundle getBundle(String bundleName) {
+ PackageAdmin packageAdmin = getPackageAdmin();
+ 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 Bundle[] getFragments(Bundle bundle) {
+ PackageAdmin packageAdmin = getPackageAdmin();
+ if (packageAdmin == null)
+ return null;
+ return packageAdmin.getFragments(bundle);
+ }
+
+ public boolean isFragment(Bundle bundle) {
+ PackageAdmin packageAdmin = getPackageAdmin();
+ if (packageAdmin == null)
+ return false;
+ return (packageAdmin.getBundleType(bundle) & PackageAdmin.BUNDLE_TYPE_FRAGMENT) > 0;
+ }
+
+ public Bundle[] getHosts(Bundle bundle) {
+ PackageAdmin packageAdmin = getPackageAdmin();
+ if (packageAdmin == null)
+ return null;
+ return packageAdmin.getHosts(bundle);
+ }
+
+ public Location getConfigurationLocation() {
+ if (configurationLocationTracker != null)
+ return (Location) configurationLocationTracker.getService();
+ else
+ return null;
+ }
+
+ public PlatformAdmin getPlatformAdmin() {
+ if (platfromTracker != null)
+ return (PlatformAdmin) platfromTracker.getService();
+ else
+ return null;
+ }
+
+}
diff --git a/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/osgi/RegistryStrategyOSGI.java b/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/osgi/RegistryStrategyOSGI.java
new file mode 100644
index 000000000..5d2067dcf
--- /dev/null
+++ b/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/osgi/RegistryStrategyOSGI.java
@@ -0,0 +1,334 @@
+/*******************************************************************************
+ * 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.registry.osgi;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.Map;
+import java.util.ResourceBundle;
+import javax.xml.parsers.SAXParserFactory;
+import org.eclipse.core.internal.registry.*;
+import org.eclipse.core.internal.runtime.ResourceTranslator;
+import org.eclipse.core.runtime.*;
+import org.eclipse.core.runtime.jobs.ISchedulingRule;
+import org.eclipse.core.runtime.jobs.Job;
+import org.eclipse.equinox.registry.spi.RegistryStrategy;
+import org.eclipse.osgi.util.NLS;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.util.tracker.ServiceTracker;
+
+/**
+ * The registry strategy that can be used in OSGi world. It provides the following functionality:
+ *
+ * - Event scheduling is done using Eclipse job scheduling mechanism
+ * - Translation is done with Equinox ResourceTranslator
+ * - Uses OSGi bundles for namespace resolution (contributors: plugins and fragments)
+ * - Registry is filled with information stored in plugin.xml / fragment.xml of OSGi bundles
+ * - Uses bunlde-based class loading to create executable extensions
+ * - Performs registry validation based on the time stamps of the plugin.xml / fragment.xml files
+ * - XML parser is obtained via an OSGi service
+ *
+ * @since org.eclipse.equinox.registry 1.0
+ *
+ */
+
+public class RegistryStrategyOSGI extends RegistryStrategy {
+
+ /**
+ * Registry access key
+ */
+ private Object token;
+
+ /**
+ * Debug extension registry
+ */
+ protected boolean DEBUG;
+
+ /**
+ * Debug extension registry events
+ */
+ protected boolean DEBUG_REGISTRY_EVENTS;
+
+ /**
+ * Tracker for the XML parser service
+ */
+ private ServiceTracker xmlTracker = null;
+
+ /**
+ * @param theStorageDir - file system directory to store cache files; might be null
+ * @param cacheReadOnly - true: cache is read only; false: cache is read/write
+ * @param key - control key for the registry (should be the same key as used in
+ * the RegistryManager#createExtensionRegistry() of this registry
+ */
+ public RegistryStrategyOSGI(File theStorageDir, boolean cacheReadOnly, Object key) {
+ super(theStorageDir, cacheReadOnly);
+ token = key;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.equinox.registry.spi.RegistryStrategy#isModifiable()
+ */
+ public boolean isModifiable() {
+ return false; // Clients are not allowed to add information into this registry
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.equinox.registry.spi.RegistryStrategy#translate(java.lang.String, java.util.ResourceBundle)
+ */
+ public final String translate(String key, ResourceBundle resources) {
+ return ResourceTranslator.getResourceString(null, key, resources);
+ }
+
+ //////////////////////////////////////////////////////////////////////////////////////////
+ // Use Eclipse job scheduling mechanism
+
+ private final static class ExtensionEventDispatcherJob extends Job {
+ // an "identy rule" that forces extension events to be queued
+ private final static ISchedulingRule EXTENSION_EVENT_RULE = new ISchedulingRule() {
+ public boolean contains(ISchedulingRule rule) {
+ return rule == this;
+ }
+
+ public boolean isConflicting(ISchedulingRule rule) {
+ return rule == this;
+ }
+ };
+ private Map deltas;
+ private Object[] listenerInfos;
+ private Object registry;
+
+ public ExtensionEventDispatcherJob(Object[] listenerInfos, Map deltas, Object registry) {
+ // name not NL'd since it is a system job
+ super("Registry event dispatcher"); //$NON-NLS-1$
+ setSystem(true);
+ this.listenerInfos = listenerInfos;
+ this.deltas = deltas;
+ this.registry = registry;
+ // all extension event dispatching jobs use this rule
+ setRule(EXTENSION_EVENT_RULE);
+ }
+
+ public IStatus run(IProgressMonitor monitor) {
+ return RegistryStrategy.processChangeEvent(listenerInfos, deltas, registry);
+ }
+ }
+ /* (non-Javadoc)
+
+ * @see org.eclipse.equinox.registry.spi.RegistryStrategy#scheduleChangeEvent(java.lang.Object[], java.util.Map, java.lang.Object)
+ */
+ public final void scheduleChangeEvent(Object[] listeners, Map deltas, Object registry) {
+ new ExtensionEventDispatcherJob(listeners, deltas, registry).schedule();
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////////////
+ // Use OSGi bundles for namespace resolution (contributors: plugins and fragments)
+
+ static private Bundle getContributingBundle(long contributingBundleId) {
+ return Activator.getContext().getBundle(contributingBundleId);
+ }
+
+ static private Bundle getNamespaceBundle(long contributingBundleId) {
+ Bundle contributingBundle = getContributingBundle(contributingBundleId);
+ if (contributingBundle == null) // When restored from disk the underlying bundle may have been uninstalled
+ throw new IllegalStateException("Internal error in extension registry. The bundle corresponding to this contribution has been uninstalled."); //$NON-NLS-1$
+ if (OSGIUtils.getDefault().isFragment(contributingBundle))
+ return OSGIUtils.getDefault().getHosts(contributingBundle)[0];
+ return contributingBundle;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.equinox.registry.spi.RegistryStrategy#getNamespaceOwnerId(long)
+ */
+ public long getNamespaceOwnerId(long contributorId) {
+ Bundle namespaceBundle = getNamespaceBundle(contributorId);
+ if (namespaceBundle == null)
+ return -1;
+ return namespaceBundle.getBundleId();
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.equinox.registry.spi.RegistryStrategy#getNamespace(long)
+ */
+ public String getNamespace(long contributorId) {
+ if (contributorId == -1)
+ return null;
+ Bundle namespaceBundle = getNamespaceBundle(contributorId);
+ if (namespaceBundle == null)
+ return null;
+ return namespaceBundle.getSymbolicName();
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.equinox.registry.spi.RegistryStrategy#getContributorIds(java.lang.String)
+ */
+ public long[] getNamespaceContributors(String namespace) {
+ Bundle correspondingHost = OSGIUtils.getDefault().getBundle(namespace);
+ if (correspondingHost == null)
+ return new long[0];
+ Bundle[] fragments = OSGIUtils.getDefault().getFragments(correspondingHost);
+ if (fragments == null)
+ return new long[] {correspondingHost.getBundleId()};
+ long[] result = new long[fragments.length + 1];
+ for (int i = 0; i < fragments.length; i++) {
+ result[i] = fragments[i].getBundleId();
+ }
+ result[fragments.length] = correspondingHost.getBundleId();
+ return result;
+ }
+
+ /////////////////////////////////////////////////////////////////////////////////////
+ // Executable extensions: bundle-based class loading
+
+ /* (non-Javadoc)
+ * @see org.eclipse.equinox.registry.spi.RegistryStrategy#createExecutableExtension(java.lang.String, long, java.lang.String, java.lang.String, java.lang.Object, java.lang.String, org.eclipse.equinox.registry.IConfigurationElement)
+ */
+ public Object createExecutableExtension(String pluginName, long namespaceOwnerId, String namespaceName, String className, Object initData, String propertyName, org.eclipse.equinox.registry.IConfigurationElement confElement) throws CoreException {
+
+ if (pluginName != null && !pluginName.equals("") && !pluginName.equals(namespaceName)) { //$NON-NLS-1$
+ Bundle otherBundle = null;
+ otherBundle = OSGIUtils.getDefault().getBundle(pluginName);
+ return createExecutableExtension(otherBundle, className, initData, propertyName, confElement);
+ }
+
+ Bundle contributingBundle = Activator.getContext().getBundle(namespaceOwnerId);
+ return createExecutableExtension(contributingBundle, className, initData, propertyName, confElement);
+ }
+
+ private Object createExecutableExtension(Bundle bundle, String className, Object initData, String propertyName, org.eclipse.equinox.registry.IConfigurationElement confElement) throws CoreException {
+ // load the requested class from this plugin
+ Class classInstance = null;
+ try {
+ classInstance = bundle.loadClass(className);
+ } catch (Exception e1) {
+ throwException(NLS.bind(RegistryMessages.plugin_loadClassError, bundle.getSymbolicName(), className), e1);
+ } catch (LinkageError e) {
+ throwException(NLS.bind(RegistryMessages.plugin_loadClassError, bundle.getSymbolicName(), className), e);
+ }
+
+ // create a new instance
+ Object result = null;
+ try {
+ result = classInstance.newInstance();
+ } catch (Exception e) {
+ throwException(NLS.bind(RegistryMessages.plugin_instantiateClassError, bundle.getSymbolicName(), className), e);
+ }
+
+ return result;
+ }
+
+ private void throwException(String message, Throwable exception) throws CoreException {
+ throw new CoreException(new Status(IStatus.ERROR, RegistryMessages.OWNER_NAME, IRegistryConstants.PLUGIN_ERROR, message, exception));
+ }
+
+ /////////////////////////////////////////////////////////////////////////////////////
+ // Start / stop extra processing: adding bundle listener; fill registry if not filled from cache
+
+ /**
+ * Listening to the bunlde events.
+ */
+ private EclipseBundleListener pluginBundleListener = null;
+
+ /* (non-Javadoc)
+ * @see org.eclipse.equinox.registry.spi.RegistryStrategy#start(java.lang.Object)
+ */
+ public void onStart(Object registry) {
+ super.onStart(registry);
+ if (!(registry instanceof ExtensionRegistry))
+ return;
+ ExtensionRegistry theRegistry = (ExtensionRegistry) registry;
+
+ // register a listener to catch new bundle installations/resolutions.
+ pluginBundleListener = new EclipseBundleListener(theRegistry, token);
+ Activator.getContext().addBundleListener(pluginBundleListener);
+
+ // populate the registry with all the currently installed bundles.
+ // There is a small window here while processBundles is being
+ // called where the pluginBundleListener may receive a BundleEvent
+ // to add/remove a bundle from the registry. This is ok since
+ // the registry is a synchronized object and will not add the
+ // same bundle twice.
+ if (!theRegistry.filledFromCache())
+ pluginBundleListener.processBundles(Activator.getContext().getBundles());
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.equinox.registry.spi.RegistryStrategy#stop(java.lang.Object)
+ */
+ public void onStop(Object registry) {
+ if (pluginBundleListener != null)
+ Activator.getContext().removeBundleListener(pluginBundleListener);
+ if (xmlTracker != null) {
+ xmlTracker.close();
+ xmlTracker = null;
+ }
+ super.onStop(registry);
+ }
+
+ //////////////////////////////////////////////////////////////////////////////////////
+ // Cache strategy
+
+ /* (non-Javadoc)
+ * @see org.eclipse.equinox.registry.spi.RegistryStrategy#useCache()
+ */
+ public boolean cacheUse() {
+ return !"true".equals(System.getProperty(IRegistryConstants.PROP_NO_REGISTRY_CACHE)); //$NON-NLS-1$
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.equinox.registry.spi.RegistryStrategy#lazyCacheLoading()
+ */
+ public boolean cacheLazyLoading() {
+ return !("true".equalsIgnoreCase(System.getProperty(IRegistryConstants.PROP_NO_LAZY_CACHE_LOADING))); //$NON-NLS-1$
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.equinox.registry.spi.RegistryStrategy#computeTimeStamp()
+ */
+ public long cacheComputeTimeStamp() {
+ // If the check config prop is false or not set then exit
+ if (!"true".equalsIgnoreCase(System.getProperty(IRegistryConstants.PROP_CHECK_CONFIG))) //$NON-NLS-1$
+ return 0;
+ BundleContext context = Activator.getContext();
+ if (context == null)
+ return 0;
+ Bundle[] allBundles = context.getBundles();
+ long result = 0;
+ for (int i = 0; i < allBundles.length; i++) {
+ URL pluginManifest = allBundles[i].getEntry("plugin.xml"); //$NON-NLS-1$
+ if (pluginManifest == null)
+ pluginManifest = allBundles[i].getEntry("fragment.xml"); //$NON-NLS-1$
+ if (pluginManifest == null)
+ continue;
+ try {
+ URLConnection connection = pluginManifest.openConnection();
+ result ^= connection.getLastModified() + allBundles[i].getBundleId();
+ } catch (IOException e) {
+ return 0;
+ }
+ }
+ return result;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.equinox.registry.spi.RegistryStrategy#getXMLParser()
+ */
+ public SAXParserFactory getXMLParser() {
+ if (xmlTracker == null) {
+ xmlTracker = new ServiceTracker(Activator.getContext(), SAXParserFactory.class.getName(), null);
+ xmlTracker.open();
+ }
+ return (SAXParserFactory) xmlTracker.getService();
+ }
+
+}
diff --git a/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/spi/ExtensionDescription.java b/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/spi/ExtensionDescription.java
new file mode 100644
index 000000000..c5b3f75ac
--- /dev/null
+++ b/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/spi/ExtensionDescription.java
@@ -0,0 +1,110 @@
+/*******************************************************************************
+ * 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.registry.spi;
+
+/**
+ * Describes an extension to be added to the extension registry.
+ *
+ * It is expected that extension name is not null. Properties and children of
+ * the extension description might be null; value might be null as well.
+ *
+ * This class is not intended to be subclassed.
+ *
+ * @since org.eclipse.equinox.registry 1.0
+ */
+public final class ExtensionDescription {
+
+ /**
+ * Name of the extension.
+ */
+ private String elementName;
+
+ /**
+ * Properties of the extension element.
+ * @see ExtensionProperty
+ */
+ private ExtensionProperty[] properties;
+
+ /**
+ * String value to be stored in this configuration element.
+ */
+ private String value;
+
+ /**
+ * Children of the extension element.
+ */
+ private ExtensionDescription[] children;
+
+ /**
+ * Constructor.
+ * @param name - name of the extension
+ * @param properties - properties of the extension
+ * @param value - string value to be stored
+ * @param children - children of the extension element
+ */
+ public ExtensionDescription(String name, ExtensionProperty[] properties, String value, ExtensionDescription[] children) {
+ this.elementName = name;
+ this.properties = properties;
+ this.value = value;
+ this.children = children;
+ }
+
+ /**
+ * Constructor.
+ * @param name - name of the extension
+ * @param property - property of the extension
+ * @param value - string value to be stored
+ * @param children - children of the extension element
+ */
+ public ExtensionDescription(String name, ExtensionProperty property, String value, ExtensionDescription[] children) {
+ this.elementName = name;
+ this.properties = new ExtensionProperty[] {property};
+ this.value = value;
+ this.children = children;
+ }
+
+ /**
+ * @return - children of the extension
+ */
+ public ExtensionDescription[] getChildren() {
+ return children;
+ }
+
+ /**
+ * @return - extension name
+ */
+ public String getElementName() {
+ return elementName;
+ }
+
+ /**
+ * @return - properties of the extension description
+ */
+ public ExtensionProperty[] getProperties() {
+ return properties;
+ }
+
+ /**
+ * @return true - extension description has some properties associated with it; false - no properties
+ * are associated with the extension description
+ */
+ public boolean hasProperties() {
+ return (properties != null) ? properties.length != 0 : false;
+ }
+
+ /**
+ * @return - String value to be stored in the element
+ */
+ public String getValue() {
+ return value;
+ }
+
+}
diff --git a/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/spi/ExtensionProperty.java b/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/spi/ExtensionProperty.java
new file mode 100644
index 000000000..818d5e48a
--- /dev/null
+++ b/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/internal/registry/spi/ExtensionProperty.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 Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.internal.registry.spi;
+
+/**
+ * Describes properties of an extension. Properties are pairs of strings:
+ * {property name; property value}.
+ *
+ * It is expected that both property name and property value are not null.
+ *
+ * This class is not intended to be subclassed.
+ *
+ * @since org.eclipse.equinox.registry 1.0
+ */
+public final class ExtensionProperty {
+
+ /**
+ * Property name.
+ */
+ private String name;
+
+ /**
+ * Property value
+ */
+ private String value;
+
+ /**
+ * Constructor.
+ * @param name - property name
+ * @param value - property value
+ */
+ public ExtensionProperty(String name, String value) {
+ this.name = name;
+ this.value = value;
+ }
+
+ /**
+ * @return - property name
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * @return - property value
+ */
+ public String getValue() {
+ return value;
+ }
+}
diff --git a/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/runtime/InvalidRegistryObjectException.java b/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/runtime/InvalidRegistryObjectException.java
new file mode 100644
index 000000000..ede5e3ac8
--- /dev/null
+++ b/bundles/org.eclipse.equinox.registry/src/org/eclipse/core/runtime/InvalidRegistryObjectException.java
@@ -0,0 +1,40 @@
+/*******************************************************************************
+ * 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;
+
+/**
+ * An unchecked exception indicating that an attempt to access
+ * an extension registry object that is no longer valid.
+ * <p>
+ * This exception is thrown by methods on extension registry
+ * objects. It is not intended to be instantiated or
+ * subclassed by clients.
+ * </p>
+ *
+ * @since 3.1
+ */
+// XXX should this class be repackaged? IExecutableExtension is in an Eqinox package.
+// But: it is a pre-existing class. It has to remain in the same package for backward compatibility.
+public class InvalidRegistryObjectException extends RuntimeException {
+ /*
+ * Declare a stable serialVersionUID.
+ */
+ private static final long serialVersionUID = 1L;
+
+ private static final String MESSAGE = "Invalid registry object"; //$NON-NLS-1$
+
+ /**
+ * Creates a new exception instance with null as its detail message.
+ */
+ public InvalidRegistryObjectException() {
+ super(MESSAGE);
+ }
+}
diff --git a/bundles/org.eclipse.equinox.registry/src/org/eclipse/equinox/registry/IConfigurationElement.java b/bundles/org.eclipse.equinox.registry/src/org/eclipse/equinox/registry/IConfigurationElement.java
new file mode 100644
index 000000000..83eaa6794
--- /dev/null
+++ b/bundles/org.eclipse.equinox.registry/src/org/eclipse/equinox/registry/IConfigurationElement.java
@@ -0,0 +1,296 @@
+/*******************************************************************************
+ * 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.equinox.registry;
+
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.InvalidRegistryObjectException;
+
+/**
+ * A configuration element, with its attributes and children,
+ * directly reflects the content and structure of the extension section
+ * within the declaring plug-in's manifest (<code>plugin.xml</code>) file.
+ * <p>
+ * This interface also provides a way to create executable extension
+ * objects.
+ * </p>
+ * <p>
+ * These registry objects are intended for relatively short-term use. Clients that
+ * deal with these objects must be aware that they may become invalid if the
+ * declaring plug-in is updated or uninstalled. If this happens, all methods except
+ * {@link #isValid()} will throw {@link InvalidRegistryObjectException}.
+ * For configuration element objects, the most common case is code in a plug-in dealing
+ * with extensions contributed to one of the extension points it declares.
+ * Code in a plug-in that has declared that it is not dynamic aware (or not
+ * declared anything) can safely ignore this issue, since the registry
+ * would not be modified while it is active. However, code in a plug-in that
+ * declares that it is dynamic aware must be careful when accessing the extension
+ * and configuration element objects because they become invalid if the contributing
+ * plug-in is removed. Similarly, tools that analyze or display the extension registry
+ * are vulnerable. Client code can pre-test for invalid objects by calling {@link #isValid()},
+ * which never throws this exception. However, pre-tests are usually not sufficient
+ * because of the possibility of the extension or configuration element object becoming
+ * invalid as a result of a concurrent activity. At-risk clients must treat
+ * <code>InvalidRegistryObjectException</code> as if it were a checked exception.
+ * Also, such clients should probably register a listener with the extension registry
+ * so that they receive notification of any changes to the registry.
+ * </p>
+ * <p>
+ * This interface is not intended to be implemented by clients.
+ * </p>
+ */
+public interface IConfigurationElement {
+ /**
+ * Creates and returns a new instance of the executable extension
+ * identified by the named attribute of this configuration element.
+ * The named attribute value must contain a fully qualified name
+ * of a Java class. The class can either refer to a class implementing
+ * the executable extension or to a factory capable of returning the
+ * executable extension.
+ * <p>
+ * The specified class is instantiated using its 0-argument public constructor.
+ * <p>
+ * Then the following checks are done:<br>
+ * If the specified class implements the {@link IExecutableExtension}
+ * interface, the method {@link IExecutableExtension#setInitializationData(IConfigurationElement, String, Object)}
+ * is called, passing to the object the configuration information that was used to create it.
+ * <p>
+ * If the specified class implements {@link IExecutableExtensionFactory}
+ * interface, the method {@link IExecutableExtensionFactory#create()}
+ * is invoked.
+ * </p>
+ * <p>
+ * Unlike other methods on this object, invoking this method may activate
+ * the plug-in.
+ * </p>
+ *
+ * @param propertyName the name of the property
+ * @return the executable instance
+ * @exception CoreException if an instance of the executable extension
+ * could not be created for any reason
+ * @see IExecutableExtension#setInitializationData(IConfigurationElement, String, Object)
+ * @see IExecutableExtensionFactory
+ */
+ public Object createExecutableExtension(String propertyName) throws CoreException;
+
+ /**
+ * Returns the named attribute of this configuration element, or
+ * <code>null</code> if none.
+ * <p>
+ * The names of configuration element attributes
+ * are the same as the attribute names of the corresponding XML element.
+ * For example, the configuration markup
+ * <pre>
+ * &lt;bg pattern="stripes"/&gt;
+ * </pre>
+ * corresponds to a configuration element named <code>"bg"</code>
+ * with an attribute named <code>"pattern"</code>
+ * with attribute value <code>"stripes"</code>.
+ * </p>
+ * <p> Note that any translation specified in the plug-in manifest
+ * file is automatically applied.
+ * </p>
+ *
+ * @param name the name of the attribute
+ * @return attribute value, or <code>null</code> if none
+ * @throws InvalidRegistryObjectException if this configuration element is no longer valid
+ */
+ public String getAttribute(String name) throws InvalidRegistryObjectException;
+
+ /**
+ * Returns the named attribute of this configuration element, or
+ * <code>null</code> if none.
+ * <p>
+ * The names of configuration element attributes
+ * are the same as the attribute names of the corresponding XML element.
+ * For example, the configuration markup
+ * <pre>
+ * &lt;bg pattern="stripes"/&gt;
+ * </pre>
+ * corresponds to a configuration element named <code>"bg"</code>
+ * with an attribute named <code>"pattern"</code>
+ * with attribute value <code>"stripes"</code>.
+ * </p>
+ * <p>
+ * Note that any translation specified in the plug-in manifest
+ * file for this attribute is <b>not</b> automatically applied.
+ * </p>
+ *
+ * @param name the name of the attribute
+ * @return attribute value, or <code>null</code> if none
+ * @throws InvalidRegistryObjectException if this configuration element is no longer valid
+ */
+ public String getAttributeAsIs(String name) throws InvalidRegistryObjectException;
+
+ /**
+ * Returns the names of the attributes of this configuration element.
+ * Returns an empty array if this configuration element has no attributes.
+ * <p>
+ * The names of configuration element attributes
+ * are the same as the attribute names of the corresponding XML element.
+ * For example, the configuration markup
+ * <pre>
+ * &lt;bg color="blue" pattern="stripes"/&gt;
+ * </pre>
+ * corresponds to a configuration element named <code>"bg"</code>
+ * with attributes named <code>"color"</code>
+ * and <code>"pattern"</code>.
+ * </p>
+ *
+ * @return the names of the attributes
+ * @throws InvalidRegistryObjectException if this configuration element is no longer valid
+ */
+ public String[] getAttributeNames() throws InvalidRegistryObjectException;
+
+ /**
+ * Returns all configuration elements that are children of this
+ * configuration element.
+ * Returns an empty array if this configuration element has no children.
+ * <p>
+ * Each child corresponds to a nested
+ * XML element in the configuration markup.
+ * For example, the configuration markup
+ * <pre>
+ * &lt;view&gt;
+ * &nbsp&nbsp&nbsp&nbsp&lt;verticalHint&gt;top&lt;/verticalHint&gt;
+ * &nbsp&nbsp&nbsp&nbsp&lt;horizontalHint&gt;left&lt;/horizontalHint&gt;
+ * &lt;/view&gt;
+ * </pre>
+ * corresponds to a configuration element, named <code>"view"</code>,
+ * with two children.
+ * </p>
+ *
+ * @return the child configuration elements
+ * @throws InvalidRegistryObjectException if this configuration element is no longer valid
+ * @see #getChildren(String)
+ */
+ public IConfigurationElement[] getChildren() throws InvalidRegistryObjectException;
+
+ /**
+ * Returns all child configuration elements with the given name.
+ * Returns an empty array if this configuration element has no children
+ * with the given name.
+ *
+ * @param name the name of the child configuration element
+ * @return the child configuration elements with that name
+ * @throws InvalidRegistryObjectException if this configuration element is no longer valid
+ * @see #getChildren()
+ */
+ public IConfigurationElement[] getChildren(String name) throws InvalidRegistryObjectException;
+
+ /**
+ * Returns the extension that declares this configuration element.
+ *
+ * @return the extension
+ * @throws InvalidRegistryObjectException if this configuration element is no longer valid
+ */
+ public IExtension getDeclaringExtension() throws InvalidRegistryObjectException;
+
+ /**
+ * Returns the name of this configuration element.
+ * The name of a configuration element is the same as
+ * the XML tag of the corresponding XML element.
+ * For example, the configuration markup
+ * <pre>
+ * &lt;wizard name="Create Project"/&gt;
+ * </pre>
+ * corresponds to a configuration element named <code>"wizard"</code>.
+ *
+ * @return the name of this configuration element
+ * @throws InvalidRegistryObjectException if this configuration element is no longer valid
+ */
+ public String getName() throws InvalidRegistryObjectException;
+
+ /**
+ * Returns the element which contains this element. If this element
+ * is an immediate child of an extension, the
+ * returned value can be downcast to <code>IExtension</code>.
+ * Otherwise the returned value can be downcast to
+ * <code>IConfigurationElement</code>.
+ *
+ * @return the parent of this configuration element
+ * or <code>null</code>
+ * @throws InvalidRegistryObjectException if this configuration element is no longer valid
+ * @since 3.0
+ */
+ public Object getParent() throws InvalidRegistryObjectException;
+
+ /**
+ * Returns the text value of this configuration element.
+ * For example, the configuration markup
+ * <pre>
+ * &lt;script lang="javascript"&gt;.\scripts\cp.js&lt;/script&gt;
+ * </pre>
+ * corresponds to a configuration element <code>"script"</code>
+ * with value <code>".\scripts\cp.js"</code>.
+ * <p> Values may span multiple lines (i.e., contain carriage returns
+ * and/or line feeds).
+ * <p> Note that any translation specified in the plug-in manifest
+ * file is automatically applied.
+ * </p>
+ *
+ * @return the text value of this configuration element or <code>null</code>
+ * @throws InvalidRegistryObjectException if this configuration element is no longer valid
+ */
+ public String getValue() throws InvalidRegistryObjectException;
+
+ /**
+ * Returns the untranslated text value of this configuration element.
+ * For example, the configuration markup
+ * <pre>
+ * &lt;script lang="javascript"&gt;.\scripts\cp.js&lt;/script&gt;
+ * </pre>
+ * corresponds to a configuration element <code>"script"</code>
+ * with value <code>".\scripts\cp.js"</code>.
+ * <p> Values may span multiple lines (i.e., contain carriage returns
+ * and/or line feeds).
+ * <p>
+ * Note that translation specified in the plug-in manifest
+ * file is <b>not</b> automatically applied.
+ * For example, the configuration markup
+ * <pre>
+ * &lt;tooltip&gt;#hattip&lt;/tooltip&gt;
+ * </pre>
+ * corresponds to a configuration element, named <code>"tooltip"</code>,
+ * with value <code>"#hattip"</code>.
+ * </p>
+ *
+ * @return the untranslated text value of this configuration element or <code>null</code>
+ * @throws InvalidRegistryObjectException if this configuration element is no longer valid
+ */
+ public String getValueAsIs() throws InvalidRegistryObjectException;
+
+ /**
+ * Returns the namespace for this configuration element. This value can be used
+ * in various global facilities to discover this configuration element's contributor.
+ * <p>
+ *
+ * @return the namespace for this configuration element
+ * @throws InvalidRegistryObjectException if this configuration element is no longer valid
+ * @see Platform#getBundle(String)
+ * @see IExtensionRegistry
+ * @since 3.1
+ */
+ public String getNamespace() throws InvalidRegistryObjectException;
+
+ /* (non-javadoc)
+ * @see Object#equals(java.lang.Object)
+ */
+ public boolean equals(Object o);
+
+ /**
+ * Returns whether this configuration element object is valid.
+ *
+ * @return <code>true</code> if the object is valid, and <code>false</code>
+ * if it is no longer valid
+ * @since 3.1
+ */
+ public boolean isValid();
+}
diff --git a/bundles/org.eclipse.equinox.registry/src/org/eclipse/equinox/registry/IExecutableExtension.java b/bundles/org.eclipse.equinox.registry/src/org/eclipse/equinox/registry/IExecutableExtension.java
new file mode 100644
index 000000000..420e757a1
--- /dev/null
+++ b/bundles/org.eclipse.equinox.registry/src/org/eclipse/equinox/registry/IExecutableExtension.java
@@ -0,0 +1,120 @@
+/*******************************************************************************
+ * 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.equinox.registry;
+
+import org.eclipse.core.runtime.CoreException;
+
+/**
+ * Interface for executable extension classes that require access to
+ * their configuration element, or implement an extension adapter.
+ * <p>
+ * Extension adapters are typically required in cases where the extension
+ * implementation does not follow the interface rules specified
+ * by the provider of the extension point. In these
+ * cases, the role of the adapter is to map between the extension point
+ * interface, and the actual extension implementation. In general, adapters
+ * are used when attempting to plug-in existing Java implementations, or
+ * non-Java implementations (e.g., external executables).
+ * </p>
+ * <p>
+ * Clients may implement this interface.
+ * </p>
+ *
+ * @see IConfigurationElement#createExecutableExtension(String)
+ */
+public interface IExecutableExtension {
+ /**
+ * This method is called by the implementation of the method
+ * <code>IConfigurationElement.createExecutableExtension</code>
+ * on a newly constructed extension, passing it its relevant configuration
+ * information. Most executable extensions only make use of the first
+ * two call arguments.
+ * <p>
+ * Regular executable extensions specify their Java implementation
+ * class name as an attribute of the configuration element for the
+ * extension. For example
+ * <pre>
+ * &lt;action run="com.example.BaseAction"/&gt;
+ * </pre>
+ * In the above example, this method would be called with a reference
+ * to the <code>&lt;action&gt;</code> element (first argument), and
+ * <code>"run"</code> as the name of the attribute that defined
+ * this executable extension (second argument).
+ * </p>
+ * <p>
+ * The last parameter is for the specific use of extension adapters
+ * and is typically not used by regular executable extensions.
+ * </p>
+ * <p>
+ * There are two supported ways of associating additional
+ * adapter-specific data with the configuration in a way that
+ * is transparent to the extension point implementor:
+ * </p>
+ * <p>
+ * (1) by specifying adapter data as part of the implementation
+ * class attribute value. The Java class name can be followed
+ * by a ":" separator, followed by any adapter data in string
+ * form. For example, if the extension point specifies an attribute
+ * <code>"run"</code> to contain the name of the extension implementation,
+ * an adapter can be configured as
+ * <pre>
+ * &lt;action run="com.example.ExternalAdapter:./cmds/util.exe -opt 3"/&gt;
+ * </pre>
+ * </p>
+ * <p>
+ * (2) by converting the attribute used to specify the executable
+ * extension to a child element of the original configuration element,
+ * and specifying the adapter data in the form of xml markup.
+ * Using this form, the example above would become
+ * <pre>
+ * &lt;action&gt;
+ * &lt;<it>run</it> class="com.xyz.ExternalAdapter"&gt;
+ * &lt;parameter name="exec" value="./cmds/util.exe"/&gt;
+ * &lt;parameter name="opt" value="3"/&gt;
+ * &lt;/<it>run</it>&gt;
+ * &lt;/action&gt;
+ * </pre>
+ * </p>
+ * <p>
+ * Form (2) will typically only be
+ * used for extension points that anticipate the majority of
+ * extensions configured into it will in fact be in the form
+ * of adapters.
+ * </p>
+ * <p>
+ * In either case, the specified adapter class is instantiated using its
+ * 0-argument public constructor. The adapter data is passed as the
+ * last argument of this method. The data argument is defined as Object.
+ * It can have the following values:
+ * <ul>
+ * <li><code>null</code>, if no adapter data was supplied</li>
+ * <li>in case (1), the initialization data
+ * string is passed as a <code>String</code></li>
+ * <li>in case (2), the initialization data is passed
+ * as a <code>Hashtable</code> containing the actual
+ * parameter names and values (both <code>String</code>s)</li>
+ * </ul>
+ * </p>
+ *
+ * @param config the configuration element used to trigger this execution.
+ * It can be queried by the executable extension for specific
+ * configuration properties
+ * @param propertyName the name of an attribute of the configuration element
+ * used on the <code>createExecutableExtension(String)</code> call. This
+ * argument can be used in the cases where a single configuration element
+ * is used to define multiple executable extensions.
+ * @param data adapter data in the form of a <code>String</code>,
+ * a <code>Hashtable</code>, or <code>null</code>.
+ * @exception CoreException if error(s) detected during initialization processing
+ * @see IConfigurationElement#createExecutableExtension(String)
+ */
+ public void setInitializationData(IConfigurationElement config, String propertyName, Object data) throws CoreException;
+}
diff --git a/bundles/org.eclipse.equinox.registry/src/org/eclipse/equinox/registry/IExecutableExtensionFactory.java b/bundles/org.eclipse.equinox.registry/src/org/eclipse/equinox/registry/IExecutableExtensionFactory.java
new file mode 100644
index 000000000..3c9c47cf4
--- /dev/null
+++ b/bundles/org.eclipse.equinox.registry/src/org/eclipse/equinox/registry/IExecutableExtensionFactory.java
@@ -0,0 +1,47 @@
+/*******************************************************************************
+ * 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.equinox.registry;
+
+import org.eclipse.core.runtime.CoreException;
+
+/**
+ * This interface allows extension providers to control how the instances provided to extension-points are being created
+ * by referring to the factory instead of referring to a class. For example, the following extension to the preference page
+ * extension-point uses a factory called <code>PreferencePageFactory</code>.
+ * <code><pre>
+ * <extension point="org.eclipse.ui.preferencePages">
+ * <page name="..." class="org.eclipse.update.ui.PreferencePageFactory:org.eclipse.update.ui.preferences.MainPreferencePage">
+ * </page>
+ * </extension>
+ * </pre>
+ * </code>
+ *
+ * <p>
+ * Effectively, factories give full control over the create executable extension process.
+ * </p><p>
+ * The factories are responsible for handling the case where the concrete instance implement {@link IExecutableExtension}.
+ * </p><p>
+ * Given that factories are instantiated as executable extensions, they must provide a 0-argument public constructor.
+ * Like any other executable extension, they can configured by implementing {@link org.eclipse.core.runtime.IExecutableExtension} interface.
+ * </p>
+ *
+ * @see org.eclipse.equinox.registry.IConfigurationElement
+ * @since org.eclipse.equinox.registry 1.0
+ */
+public interface IExecutableExtensionFactory {
+ /**
+ * Creates and returns a new instance.
+ *
+ * @exception CoreException if an instance of the executable extension
+ * could not be created for any reason
+ */
+ Object create() throws CoreException;
+}
diff --git a/bundles/org.eclipse.equinox.registry/src/org/eclipse/equinox/registry/IExtension.java b/bundles/org.eclipse.equinox.registry/src/org/eclipse/equinox/registry/IExtension.java
new file mode 100644
index 000000000..1ddf82386
--- /dev/null
+++ b/bundles/org.eclipse.equinox.registry/src/org/eclipse/equinox/registry/IExtension.java
@@ -0,0 +1,132 @@
+/*******************************************************************************
+ * 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.equinox.registry;
+
+import org.eclipse.core.runtime.InvalidRegistryObjectException;
+
+/**
+ * An extension declared in a plug-in.
+ * All information is obtained from the declaring plug-in's
+ * manifest (<code>plugin.xml</code>) file.
+ * <p>
+ * These registry objects are intended for relatively short-term use. Clients that
+ * deal with these objects must be aware that they may become invalid if the
+ * declaring plug-in is updated or uninstalled. If this happens, all methods except
+ * {@link #isValid()} will throw {@link InvalidRegistryObjectException}.
+ * For extension objects, the most common case is code in a plug-in dealing
+ * with extensions contributed to one of the extension points it declares.
+ * Code in a plug-in that has declared that it is not dynamic aware (or not
+ * declared anything) can safely ignore this issue, since the registry
+ * would not be modified while it is active. However, code in a plug-in that
+ * declares that it is dynamic aware must be careful when accessing the extension
+ * objects because they become invalid if the contributing plug-in is removed.
+ * Similarly, tools that analyze or display the extension registry are vulnerable.
+ * Client code can pre-test for invalid objects by calling {@link #isValid()},
+ * which never throws this exception. However, pre-tests are usually not sufficient
+ * because of the possibility of the extension object becoming invalid as a
+ * result of a concurrent activity. At-risk clients must treat
+ * <code>InvalidRegistryObjectException</code> as if it were a checked exception.
+ * Also, such clients should probably register a listener with the extension registry
+ * so that they receive notification of any changes to the registry.
+ * </p>
+ * <p>
+ * This interface is not intended to be implemented by clients.
+ * </p>
+ */
+public interface IExtension {
+ /**
+ * Returns all configuration elements declared by this extension.
+ * These elements are a direct reflection of the configuration
+ * markup supplied in the manifest (<code>plugin.xml</code>)
+ * file for the plug-in that declares this extension.
+ * Returns an empty array if this extension does not declare any
+ * configuration elements.
+ *
+ * @return the configuration elements declared by this extension
+ * @throws InvalidRegistryObjectException if this extension is no longer valid
+ */
+ public IConfigurationElement[] getConfigurationElements() throws InvalidRegistryObjectException;
+
+ /**
+ * Returns the namespace for this extension. This value can be used
+ * in various global facilities to discover this extension's provider.
+ *
+ * @return the namespace for this extension
+ * @throws InvalidRegistryObjectException if this extension is no longer valid
+ * @see Platform#getBundle(String)
+ * @see IExtensionRegistry
+ * @since 3.0
+ */
+ public String getNamespace() throws InvalidRegistryObjectException;
+
+ /**
+ * Returns the unique identifier of the extension point
+ * to which this extension should be contributed.
+ *
+ * @return the unique identifier of the relevant extension point
+ * @throws InvalidRegistryObjectException if this extension is no longer valid
+ */
+ public String getExtensionPointUniqueIdentifier() throws InvalidRegistryObjectException;
+
+ /**
+ * Returns a displayable label for this extension.
+ * Returns the empty string if no label for this extension
+ * is specified in the plug-in manifest file.
+ * <p> Note that any translation specified in the plug-in manifest
+ * file is automatically applied.
+ * <p>
+ *
+ * @return a displayable string label for this extension,
+ * possibly the empty string
+ * @throws InvalidRegistryObjectException if this extension is no longer valid
+ */
+ public String getLabel() throws InvalidRegistryObjectException;
+
+ /**
+ * Returns the simple identifier of this extension, or <code>null</code>
+ * if this extension does not have an identifier.
+ * This identifier is specified in the plug-in manifest (<code>plugin.xml</code>)
+ * file as a non-empty string containing no period characters
+ * (<code>'.'</code>) and must be unique within the defining plug-in.
+ *
+ * @return the simple identifier of the extension (e.g. <code>"main"</code>)
+ * or <code>null</code>
+ * @throws InvalidRegistryObjectException if this extension is no longer valid
+ */
+ public String getSimpleIdentifier() throws InvalidRegistryObjectException;
+
+ /**
+ * Returns the unique identifier of this extension, or <code>null</code>
+ * if this extension does not have an identifier.
+ * If available, this identifier is unique within the plug-in registry, and
+ * is composed of the namespace where this extension
+ * was declared and this extension's simple identifier.
+ *
+ * @return the unique identifier of the extension
+ * (e.g. <code>"com.example.acme.main"</code>), or <code>null</code>
+ * @throws InvalidRegistryObjectException if this extension is no longer valid
+ */
+ public String getUniqueIdentifier() throws InvalidRegistryObjectException;
+
+ /* (non-javadoc)
+ * @see Object#equals(java.lang.Object)
+ */
+ public boolean equals(Object o);
+
+ /**
+ * Returns whether this extension object is valid.
+ *
+ * @return <code>true</code> if the object is valid, and <code>false</code>
+ * if it is no longer valid
+ * @since 3.1
+ */
+ public boolean isValid();
+}
diff --git a/bundles/org.eclipse.equinox.registry/src/org/eclipse/equinox/registry/IExtensionDelta.java b/bundles/org.eclipse.equinox.registry/src/org/eclipse/equinox/registry/IExtensionDelta.java
new file mode 100644
index 000000000..adcec1ef0
--- /dev/null
+++ b/bundles/org.eclipse.equinox.registry/src/org/eclipse/equinox/registry/IExtensionDelta.java
@@ -0,0 +1,57 @@
+/*******************************************************************************
+ * 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.equinox.registry;
+
+/**
+ * An extension delta represents changes to the extension registry.
+ * <p>
+ * This interface is not intended to be implemented by clients.
+ * </p>
+
+ * @since 3.0
+ */
+public interface IExtensionDelta {
+ /**
+ * Delta kind constant indicating that an extension has been added to an
+ * extension point.
+ * @see IExtensionDelta#getKind()
+ */
+ public int ADDED = 1;
+ /**
+ * Delta kind constant indicating that an extension has been removed from an
+ * extension point.
+ * @see IExtensionDelta#getKind()
+ */
+ public int REMOVED = 2;
+
+ /**
+ * The kind of this extension delta.
+ *
+ * @return the kind of change this delta represents
+ * @see #ADDED
+ * @see #REMOVED
+ */
+ public int getKind();
+
+ /**
+ * Returns the affected extension.
+ *
+ * @return the affected extension
+ */
+ public IExtension getExtension();
+
+ /**
+ * Returns the affected extension point.
+ *
+ * @return the affected extension point
+ */
+ public IExtensionPoint getExtensionPoint();
+}
diff --git a/bundles/org.eclipse.equinox.registry/src/org/eclipse/equinox/registry/IExtensionPoint.java b/bundles/org.eclipse.equinox.registry/src/org/eclipse/equinox/registry/IExtensionPoint.java
new file mode 100644
index 000000000..34a38cd20
--- /dev/null
+++ b/bundles/org.eclipse.equinox.registry/src/org/eclipse/equinox/registry/IExtensionPoint.java
@@ -0,0 +1,156 @@
+/*******************************************************************************
+ * 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.equinox.registry;
+
+import org.eclipse.core.runtime.InvalidRegistryObjectException;
+
+/**
+ * An extension point declared in a plug-in.
+ * Except for the list of extensions plugged in to it, the information
+ * available for an extension point is obtained from the declaring plug-in's
+ * manifest (<code>plugin.xml</code>) file.
+ * <p>
+ * These registry objects are intended for relatively short-term use. Clients that
+ * deal with these objects must be aware that they may become invalid if the
+ * declaring plug-in is updated or uninstalled. If this happens, all methods except
+ * {@link #isValid()} will throw {@link InvalidRegistryObjectException}.
+ * For extension point objects, the most common case is code in a plug-in dealing
+ * with one of the extension points it declares. These extension point objects are
+ * guaranteed to be valid while the plug-in is active. Code in a plug-in that has
+ * declared that it is not dynamic aware (or not declared anything) can also safely
+ * ignore this issue, since the registry would not be modified while it is
+ * active. However, code in a plug-in that declares that it is dynamic aware
+ * must be careful if it access the extension point object of a different plug-in,
+ * because it's at risk if that other plug-in is removed. Similarly,
+ * tools that analyze or display the extension registry are vulnerable.
+ * Client code can pre-test for invalid objects by calling {@link #isValid()},
+ * which never throws this exception. However, pre-tests are usually not sufficient
+ * because of the possibility of the extension point object becoming invalid as a
+ * result of a concurrent activity. At-risk clients must treat
+ * <code>InvalidRegistryObjectException</code> as if it were a checked exception.
+ * Also, such clients should probably register a listener with the extension registry
+ * so that they receive notification of any changes to the registry.
+ * </p>
+ * <p>
+ * This interface is not intended to be implemented by clients.
+ * </p>
+ */
+public interface IExtensionPoint {
+ /**
+ * Returns all configuration elements from all extensions configured
+ * into this extension point. Returns an empty array if this extension
+ * point has no extensions configured, or none of the extensions
+ * contain configuration elements.
+ *
+ * @return the configuration elements for all extension configured
+ * into this extension point
+ * @throws InvalidRegistryObjectException if this extension point is no longer valid
+ */
+ public IConfigurationElement[] getConfigurationElements() throws InvalidRegistryObjectException;
+
+ /**
+ * Returns the namespace for this extension point. This value can be used
+ * in various global facilities to discover this extension point's provider.
+ *
+ * @return the namespace for this extension point
+ * @throws InvalidRegistryObjectException if this extension point is no longer valid
+ * @see Platform#getBundle(String)
+ * @see IExtensionRegistry
+ * @since 3.0
+ */
+ public String getNamespace() throws InvalidRegistryObjectException;
+
+ /**
+ * Returns the extension with the given unique identifier configured into
+ * this extension point, or <code>null</code> if there is no such extension.
+ * Since an extension might not have an identifier, some extensions
+ * can only be found via the <code>getExtensions</code> method.
+ *
+ * @param extensionId the unique identifier of an extension
+ * (e.g. <code>"com.example.acme.main"</code>).
+ * @return an extension, or <code>null</code>
+ * @throws InvalidRegistryObjectException if this extension point is no longer valid
+ */
+ public IExtension getExtension(String extensionId) throws InvalidRegistryObjectException;
+
+ /**
+ * Returns all extensions configured into this extension point.
+ * Returns an empty array if this extension point has no extensions.
+ *
+ * @return the extensions configured into this extension point
+ * @throws InvalidRegistryObjectException if this extension point is no longer valid
+ */
+ public IExtension[] getExtensions() throws InvalidRegistryObjectException;
+
+ /**
+ * Returns a displayable label for this extension point.
+ * Returns the empty string if no label for this extension point
+ * is specified in the plug-in manifest file.
+ * <p> Note that any translation specified in the plug-in manifest
+ * file is automatically applied.
+ * </p>
+ *
+ * @return a displayable string label for this extension point,
+ * possibly the empty string
+ * @throws InvalidRegistryObjectException if this extension point is no longer valid
+ */
+ public String getLabel() throws InvalidRegistryObjectException;
+
+ /**
+ * Returns reference to the extension point schema. The schema
+ * reference is returned as a URL path relative to the plug-in
+ * installation URL.
+ * Returns the empty string if no schema for this extension point
+ * is specified in the plug-in manifest file.
+ *
+ * @return a relative URL path, or an empty string
+ * @throws InvalidRegistryObjectException if this extension point is no longer valid
+ */
+ public String getSchemaReference() throws InvalidRegistryObjectException;
+
+ /**
+ * Returns the simple identifier of this extension point.
+ * This identifier is a non-empty string containing no
+ * period characters (<code>'.'</code>) and is guaranteed
+ * to be unique within the defining plug-in.
+ *
+ * @return the simple identifier of the extension point (e.g. <code>"builders"</code>)
+ * @throws InvalidRegistryObjectException if this extension point is no longer valid
+ */
+ public String getSimpleIdentifier() throws InvalidRegistryObjectException;
+
+ /**
+ * Returns the unique identifier of this extension point.
+ * This identifier is unique within the plug-in registry, and
+ * is composed of the namespace for this extension point
+ * and this extension point's simple identifier.
+ *
+ *
+ * @return the unique identifier of the extension point
+ * (e.g. <code>"org.eclipse.core.resources.builders"</code>)
+ * @throws InvalidRegistryObjectException if this extension point is no longer valid
+ */
+ public String getUniqueIdentifier() throws InvalidRegistryObjectException;
+
+ /* (non-javadoc)
+ * @see Object#equals(java.lang.Object)
+ */
+ public boolean equals(Object o);
+
+ /**
+ * Returns whether this extension point object is valid.
+ *
+ * @return <code>true</code> if the object is valid, and <code>false</code>
+ * if it is no longer valid
+ * @since 3.1
+ */
+ public boolean isValid();
+}
diff --git a/bundles/org.eclipse.equinox.registry/src/org/eclipse/equinox/registry/IExtensionRegistry.java b/bundles/org.eclipse.equinox.registry/src/org/eclipse/equinox/registry/IExtensionRegistry.java
new file mode 100644
index 000000000..552f5fabd
--- /dev/null
+++ b/bundles/org.eclipse.equinox.registry/src/org/eclipse/equinox/registry/IExtensionRegistry.java
@@ -0,0 +1,285 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 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.equinox.registry;
+
+import java.io.InputStream;
+import java.util.EventListener;
+import java.util.ResourceBundle;
+
+/**
+ * The extension registry holds the master list of all
+ * discovered namespaces, extension points and extensions.
+ * <p>
+ * The extension registry can be queried, by name, for
+ * extension points and extensions.
+ * </p>
+ * <p>
+ * The various objects that describe the contents of the extension registry
+ * ({@link IExtensionPoint}, {@link IExtension}, and {@link IConfigurationElement})
+ * are intended for relatively short-term use. Clients that deal with these objects
+ * must be aware that they may become invalid if the declaring plug-in is updated
+ * or uninstalled. If this happens, all methods on these object except
+ * <code>isValid()</code> will throw {@link InvalidRegistryObjectException}.
+ * Code in a plug-in that has declared that it is not dynamic aware (or not declared
+ * anything) can safely ignore this issue, since the registry would not be
+ * modified while it is active. However, code in a plug-in that declares that it
+ * is dynamic aware must be careful if it accesses extension registry objects,
+ * because it's at risk if plug-in are removed. Similarly, tools that analyze
+ * or display the extension registry are vulnerable. Client code can pre-test for
+ * invalid objects by calling <code>isValid()</code>, which never throws this exception.
+ * However, pre-tests are usually not sufficient because of the possibility of the
+ * extension registry object becoming invalid as a result of a concurrent activity.
+ * At-risk clients must treat <code>InvalidRegistryObjectException</code> as if it
+ * were a checked exception. Also, such clients should probably register a listener
+ * with the extension registry so that they receive notification of any changes to
+ * the registry.
+ * </p>
+ * <p>
+ * Extensions and extension points are declared by generic entities called
+ * <cite>namespaces</cite>. The only fact known about namespaces is that they
+ * have unique string-based identifiers. One example of a namespace
+ * is a plug-in, for which the namespace id is the plug-in id.
+ * </p>
+ * <p>
+ * This interface is not intended to be implemented by clients.
+ * </p>
+ * @since 3.0
+ */
+public interface IExtensionRegistry {
+ /**
+ * Adds the given listener for registry change events related to extension points
+ * in the given namespace.
+ * Has no effect if an identical listener is already registered. After
+ * completion of this method, the given listener will be registered for events
+ * related to extension points in the specified namespace. If no namespace
+ * is specified, the listener will receive notifications for changes to
+ * extension points in any namespace.
+ * <p>
+ * Once registered, a listener starts receiving notification of changes to
+ * the registry. Registry change notifications are sent asynchronously.
+ * The listener continues to receive notifications until it is removed.
+ * </p>
+ * @param listener the listener
+ * @param namespace the namespace in which to listen for changes
+ * @see IRegistryChangeListener
+ * @see IRegistryChangeEvent
+ * @see #removeRegistryChangeListener(IRegistryChangeListener)
+ */
+ public void addRegistryChangeListener(EventListener listener, String namespace);
+
+ /**
+ * Adds the given listener for registry change events.
+ * Has no effect if an identical listener is already registered.
+ *
+ * <p>
+ * This method is equivalent to:
+ * <pre>
+ * addRegistryChangeListener(listener,null);
+ * </pre>
+ * </p>
+ *
+ * @param listener the listener
+ * @see IRegistryChangeListener
+ * @see IRegistryChangeEvent
+ * @see #addRegistryChangeListener(IRegistryChangeListener, String)
+ * @see #removeRegistryChangeListener(IRegistryChangeListener)
+ */
+ public void addRegistryChangeListener(EventListener listener);
+
+ /**
+ * Returns all configuration elements from all extensions configured
+ * into the identified extension point. Returns an empty array if the extension
+ * point does not exist, has no extensions configured, or none of the extensions
+ * contain configuration elements.
+ *
+ * @param extensionPointId the unique identifier of the extension point
+ * (e.g. <code>"org.eclipse.core.resources.builders"</code>)
+ * @return the configuration elements
+ */
+ public IConfigurationElement[] getConfigurationElementsFor(String extensionPointId);
+
+ /**
+ * Returns all configuration elements from all extensions configured
+ * into the identified extension point. Returns an empty array if the extension
+ * point does not exist, has no extensions configured, or none of the extensions
+ * contain configuration elements.
+ *
+ * @param namespace the namespace for the extension point
+ * (e.g. <code>"org.eclipse.core.resources"</code>)
+ * @param extensionPointName the simple identifier of the
+ * extension point (e.g. <code>"builders"</code>)
+ * @return the configuration elements
+ */
+ public IConfigurationElement[] getConfigurationElementsFor(String namespace, String extensionPointName);
+
+ /**
+ * Returns all configuration elements from the identified extension.
+ * Returns an empty array if the extension does not exist or
+ * contains no configuration elements.
+ *
+ * @param namespace the namespace for the extension point
+ * (e.g. <code>"org.eclipse.core.resources"</code>)
+ * @param extensionPointName the simple identifier of the
+ * extension point (e.g. <code>"builders"</code>)
+ * @param extensionId the unique identifier of the extension
+ * (e.g. <code>"com.example.acme.coolbuilder</code>)
+ * @return the configuration elements
+ */
+ public IConfigurationElement[] getConfigurationElementsFor(String namespace, String extensionPointName, String extensionId);
+
+ /**
+ * Returns the specified extension in this extension registry,
+ * or <code>null</code> if there is no such extension.
+ *
+ * @param extensionId the unique identifier of the extension
+ * (e.g. <code>"com.example.acme.coolbuilder"</code>)
+ * @return the extension, or <code>null</code>
+ */
+ public IExtension getExtension(String extensionId);
+
+ /**
+ * Returns the specified extension in this extension registry,
+ * or <code>null</code> if there is no such extension.
+ * The first parameter identifies the extension point, and the second
+ * parameter identifies an extension plugged in to that extension point.
+ *
+ * @param extensionPointId the unique identifier of the extension point
+ * (e.g. <code>"org.eclipse.core.resources.builders"</code>)
+ * @param extensionId the unique identifier of the extension
+ * (e.g. <code>"com.example.acme.coolbuilder"</code>)
+ * @return the extension, or <code>null</code>
+ */
+ public IExtension getExtension(String extensionPointId, String extensionId);
+
+ /**
+ * Returns the specified extension in this extension registry,
+ * or <code>null</code> if there is no such extension.
+ * The first two parameters identify the extension point, and the third
+ * parameter identifies an extension plugged in to that extension point.
+ *
+ * @param namespace the namespace for the extension point
+ * (e.g. <code>"org.eclipse.core.resources"</code>)
+ * @param extensionPointName the simple identifier of the
+ * extension point (e.g. <code>"builders"</code>)
+ * @param extensionId the unique identifier of the extension
+ * (e.g. <code>"com.example.acme.coolbuilder"</code>)
+ * @return the extension, or <code>null</code>
+ */
+ public IExtension getExtension(String namespace, String extensionPointName, String extensionId);
+
+ /**
+ * Returns the extension point with the given extension point identifier
+ * in this extension registry, or <code>null</code> if there is no such
+ * extension point.
+ *
+ * @param extensionPointId the unique identifier of the extension point
+ * (e.g., <code>"org.eclipse.core.resources.builders"</code>)
+ * @return the extension point, or <code>null</code>
+ */
+ public IExtensionPoint getExtensionPoint(String extensionPointId);
+
+ /**
+ * Returns the extension point in this extension registry
+ * with the given namespace and extension point simple identifier,
+ * or <code>null</code> if there is no such extension point.
+ *
+ * @param namespace the namespace for the given extension point
+ * (e.g. <code>"org.eclipse.core.resources"</code>)
+ * @param extensionPointName the simple identifier of the
+ * extension point (e.g. <code>" builders"</code>)
+ * @return the extension point, or <code>null</code>
+ */
+ public IExtensionPoint getExtensionPoint(String namespace, String extensionPointName);
+
+ /**
+ * Returns all extension points known to this extension registry.
+ * Returns an empty array if there are no extension points.
+ *
+ * @return the extension points known to this extension registry
+ */
+ public IExtensionPoint[] getExtensionPoints();
+
+ /**
+ * Returns all extension points declared in the given namespace. Returns an empty array if
+ * there are no extension points declared in the namespace.
+ *
+ * @param namespace the namespace for the extension points
+ * (e.g. <code>"org.eclipse.core.resources"</code>)
+ * @return the extension points in this registry declared in the given namespace
+ */
+ public IExtensionPoint[] getExtensionPoints(String namespace);
+
+ /**
+ * Returns all extensions declared in the given namespace. Returns an empty array if
+ * no extensions are declared in the namespace.
+ *
+ * @param namespace the namespace for the extensions
+ * (e.g. <code>"org.eclipse.core.resources"</code>)
+ * @return the extensions in this registry declared in the given namespace
+ */
+ public IExtension[] getExtensions(String namespace);
+
+ /**
+ * Returns all namespaces where extensions and/or extension points. Returns an
+ * empty array if there are no known extensions/extension points in this registry.
+ *
+ * @return all namespaces known to this registry
+ * @since 3.0
+ */
+ //TODO This needs to be clarified.
+ public String[] getNamespaces();
+
+ /**
+ * Removes the given registry change listener from this registry.
+ * Has no effect if an identical listener is not registered.
+ *
+ * @param listener the listener
+ * @see IRegistryChangeListener
+ * @see #addRegistryChangeListener(IRegistryChangeListener)
+ * @see #addRegistryChangeListener(IRegistryChangeListener, String)
+ */
+ public void removeRegistryChangeListener(EventListener listener);
+
+ /**
+ * Adds to the extension registry an extension point(s), extension(s), or
+ * a combination of those described by the XML file.
+ *
+ * If registry is no modifiable, this method is an access controlled method.
+ * Proper token is required for non-modifiable registries.
+ *
+ * @see RegistryStrategy.isModifiable
+ *
+ * @param is - stream open on the XML file. The XML file can contain an extension
+ * poin(s) or/and extension(s) described in the format similar to plugin.xml
+ * @param contributorId - ID of the supplier of this contribution
+ * @param type - name of the top tag in the XML file. At present it must be either
+ * "plugin" or "fragment"
+ * @param name - name of the file containing this contribution for log purposes
+ * @param xmlTranslationBundle - resource bundle used for translations; might be null
+ * @param token - control key for the registry (should be the same key as used in
+ * the RegistryManager#createExtensionRegistry() of this registry
+ * @return - true: the contribution was successfully processed; false - error in
+ * the processing of the contribution
+ */
+ public boolean addXMLContribution(InputStream is, long contributorId, String type, String name, ResourceBundle xmlTranslationBundle, Object token);
+
+ /**
+ * Call this method to properly stop the registry. It stops registry event processing
+ * and writes out cache information to be used in the next run.
+ *
+ * This is an access controlled method; proper token is required.
+ *
+ * @param registry - the registry to be stopped
+ * @param token - control key for the registry (should be the same key as used in
+ * the RegistryManager#createExtensionRegistry() of this registry
+ */
+ public void stop(Object token);
+}
diff --git a/bundles/org.eclipse.equinox.registry/src/org/eclipse/equinox/registry/IRegistryChangeEvent.java b/bundles/org.eclipse.equinox.registry/src/org/eclipse/equinox/registry/IRegistryChangeEvent.java
new file mode 100644
index 000000000..6a5364d8d
--- /dev/null
+++ b/bundles/org.eclipse.equinox.registry/src/org/eclipse/equinox/registry/IRegistryChangeEvent.java
@@ -0,0 +1,62 @@
+/*******************************************************************************
+ * 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.equinox.registry;
+
+/**
+ * Registry change events describe changes to the extension registry.
+ * <p>
+ * This interface is not intended to be implemented by clients.
+ * </p>
+ * @since 3.0
+ * @see IExtensionRegistry
+ * @see IRegistryChangeListener
+ */
+public interface IRegistryChangeEvent {
+ /**
+ * Returns all extension deltas for all hosts. Returns an empty array if there are
+ * no deltas in this event.
+ *
+ * @return all extension deltas
+ */
+ public IExtensionDelta[] getExtensionDeltas();
+
+ /**
+ * Returns all extension deltas for the given namespace. Returns an empty array if there are
+ * no deltas in this event for any extension points provided in the given namespace.
+ *
+ * @param namespace the namespace for the extension deltas
+ * @return all extension deltas for the given namespace
+ */
+ public IExtensionDelta[] getExtensionDeltas(String namespace);
+
+ /**
+ * Returns all the extension deltas for the given namespace and extension point. Returns an
+ * empty array if there are no deltas in this event for the given extension point.
+ *
+ * @param namespace the namespace for the extension point
+ * @param extensionPoint the simple identifier of the
+ * extension point (e.g. <code>"builders"</code>)
+ * @return all extension deltas for the given extension point
+ */
+ public IExtensionDelta[] getExtensionDeltas(String namespace, String extensionPoint);
+
+ /**
+ * Returns the delta for the given namespace, extension point and extension.
+ * Returns <code>null</code> if none exists in this event.
+ *
+ * @param namespace the namespace for the extension point
+ * @param extensionPoint the simple identifier of the
+ * extension point (e.g. <code>"builders"</code>)
+ * @param extension the unique identifier of the extension
+ * @return the extension delta, or <code>null</code>
+ */
+ public IExtensionDelta getExtensionDelta(String namespace, String extensionPoint, String extension);
+}
diff --git a/bundles/org.eclipse.equinox.registry/src/org/eclipse/equinox/registry/IRegistryChangeListener.java b/bundles/org.eclipse.equinox.registry/src/org/eclipse/equinox/registry/IRegistryChangeListener.java
new file mode 100644
index 000000000..7d627737b
--- /dev/null
+++ b/bundles/org.eclipse.equinox.registry/src/org/eclipse/equinox/registry/IRegistryChangeListener.java
@@ -0,0 +1,43 @@
+/*******************************************************************************
+ * 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.equinox.registry;
+
+import java.util.EventListener;
+
+/**
+ * A registry change listener is notified of changes to extensions points in the
+ * registry. These changes arise from subsequent manipulation of the registry after
+ * it was initially created.
+ * <p>
+ * Clients may implement this interface.
+ * </p>
+ *
+ * @since 3.0
+ * @see IExtensionRegistry
+ * @see IRegistryChangeEvent
+ */
+public interface IRegistryChangeListener extends EventListener {
+ /**
+ * Notifies this listener that some registry changes are happening, or have
+ * already happened.
+ * <p>
+ * The supplied event gives details. This event object (and the deltas in it) is valid
+ * only for the duration of the invocation of this method.
+ * </p> <p>
+ * Note: This method is called by the platform; it is not intended
+ * to be called directly by clients.
+ * </p>
+ *
+ * @param event the registry change event
+ * @see IRegistryChangeEvent
+ */
+ public void registryChanged(IRegistryChangeEvent event);
+}
diff --git a/bundles/org.eclipse.equinox.registry/src/org/eclipse/equinox/registry/RegistryFactory.java b/bundles/org.eclipse.equinox.registry/src/org/eclipse/equinox/registry/RegistryFactory.java
new file mode 100644
index 000000000..94ec1fd87
--- /dev/null
+++ b/bundles/org.eclipse.equinox.registry/src/org/eclipse/equinox/registry/RegistryFactory.java
@@ -0,0 +1,56 @@
+/*******************************************************************************
+ * 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.equinox.registry;
+
+import java.io.File;
+import org.eclipse.core.internal.registry.ExtensionRegistry;
+import org.eclipse.core.internal.registry.osgi.RegistryStrategyOSGI;
+import org.eclipse.equinox.registry.spi.RegistryStrategy;
+
+/**
+ * Use this class to create an extension registry.
+ *
+ * This class is not intended to be subclassed or instantiated.
+ *
+ * @since org.eclipse.equinox.registry 1.0
+ */
+public final class RegistryFactory {
+
+ /**
+ * Creates an extension registry.
+ *
+ * @param strategy - optional strategies that modify registry functionality; might be null
+ * @param token - control token for the registry. Keep it to access controlled methods of the
+ * registry
+ * @return - new extension registry
+ */
+ public static IExtensionRegistry createExtensionRegistry(RegistryStrategy strategy, Object token) {
+ return new ExtensionRegistry(strategy, token);
+ }
+
+ /**
+ * Creates registry strategy that can be used in OSGi world. It provides the following functionality:
+ * - Event scheduling is done using Eclipse job scheduling mechanism
+ * - Translation is done with Equinox ResourceTranslator
+ * - Uses OSGi bundle model for namespace resolution
+ * - Uses bunlde-based class loaders to create executable extensions
+ * - Registry is filled with information stored in plugin.xml / fragment.xml files of OSGi bundles
+ * with the XML parser is obtained via an OSGi service
+ * - Performs registry validation based on the time stamps of the plugin.xml / fragment.xml files
+ *
+ * @param storageDir - file system directory to store cache files; might be null
+ * @param cacheReadOnly - true: cache is read only; false: cache is read/write
+ * @param token - control token for the registry
+ */
+ public static RegistryStrategy createOSGiStrategy(File storageDir, boolean cacheReadOnly, Object token) {
+ return new RegistryStrategyOSGI(storageDir, cacheReadOnly, token);
+ }
+}
diff --git a/bundles/org.eclipse.equinox.registry/src/org/eclipse/equinox/registry/spi/RegistryStrategy.java b/bundles/org.eclipse.equinox.registry/src/org/eclipse/equinox/registry/spi/RegistryStrategy.java
new file mode 100644
index 000000000..a7d301e72
--- /dev/null
+++ b/bundles/org.eclipse.equinox.registry/src/org/eclipse/equinox/registry/spi/RegistryStrategy.java
@@ -0,0 +1,377 @@
+/*******************************************************************************
+ * 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.equinox.registry.spi;
+
+import java.io.File;
+import java.util.Map;
+import java.util.ResourceBundle;
+import javax.xml.parsers.SAXParserFactory;
+import org.eclipse.core.internal.registry.*;
+import org.eclipse.core.runtime.*;
+import org.eclipse.equinox.registry.IConfigurationElement;
+import org.eclipse.equinox.registry.IExtensionRegistry;
+import org.eclipse.osgi.util.NLS;
+
+/**
+ * This is the basic registry strategy. It describes how registry does logging,
+ * message translation, namespace resolution, extra start/stop processing, event scheduling,
+ * caching, and debugging.
+ *
+ * In this strategy:
+ * - Clients can add information into the registry
+ * - Caching is enabled and doesn't use state or time stamp validation; no alternative cache location is supplied
+ * - Logging is done onto System.out
+ * - All registry contributors are assumed to reside in separate namespaces
+ * - Standard Java class loading is used to create executable extensions
+ *
+ * This class can be overridden and/or instantiated.
+ *
+ * @since org.eclipse.equinox.registry 1.0
+ */
+public class RegistryStrategy {
+
+ /**
+ * File system directory to store cache files; might be null
+ */
+ private final File theStorageDir;
+
+ /**
+ * Specifies if the registry file cache is read only
+ */
+ private final boolean cacheReadOnly;
+
+ /**
+ * Public constructor.
+ *
+ * @param theStorageDir - file system directory to store cache files; might be null
+ * @param cacheReadOnly - true: cache is read only; false: cache is read/write
+ */
+ public RegistryStrategy(File theStorageDir, boolean cacheReadOnly) {
+ this.theStorageDir = theStorageDir;
+ this.cacheReadOnly = cacheReadOnly;
+ }
+
+ public final File getStorage() {
+ return theStorageDir;
+ }
+
+ public final boolean isCacheReadOnly() {
+ return cacheReadOnly;
+ }
+
+ /**
+ * Specifies if registry clients can add information into the registry.
+ * @return true: clients can add information; false: proper token should be supplied
+ * in order to add information into the registry.
+ */
+ public boolean isModifiable() {
+ return true;
+ }
+
+ /**
+ * Adds a log entry based on the supplied status.
+ * This method writes a message to the System.out in the following format:
+ *
+ * [Error|Warning|Log]: Main error message
+ * [Error|Warning|Log]: Child error message 1
+ * ...
+ * [Error|Warning|Log]: Child error message N
+ *
+ * @param status - the status to log
+ */
+ public void log(IStatus status) {
+ RegistrySupport.log(status, null);
+ }
+
+ /**
+ * Translates key using the supplied resource bundle. The resource bundle is
+ * optional and might be null.
+ * The default translation routine assumes that keys are prefixed with '%'. If
+ * no resource bundle is present, the key itself (without leading '%') is returned.
+ * There is no decoding for the leading '%%'.
+ *
+ * @param key - message key to be translated
+ * @param resources - resource bundle, might be null
+ * @return - translated string
+ */
+ public String translate(String key, ResourceBundle resources) {
+ return RegistrySupport.translate(key, resources);
+ }
+
+ /**
+ * Override this method to provide additional processing performed
+ * at the end of the registry's constructor.
+ *
+ * @param registry - the extension registry being created
+ */
+ public void onStart(Object registry) {
+ // The default implementation
+ }
+
+ /**
+ * Override this method to provide additional processing before
+ * the extension registry's stop() method is executed.
+ *
+ * @param registry - the extension registry being stopped
+ */
+ public void onStop(Object registry) {
+ // The default implementation
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // XXX The use of longs is strange here. It is very OSGi specific.
+ // It is likey that they are going to be replaced with some
+ // generic object in the near future.
+
+ /**
+ * Returns Id of the namespace owner that given contributor resides in.
+ *
+ * Override this method to supply namespace resolution capabilities to
+ * the extension registry. If this interface is not overridden, each contributor
+ * is assumed to be in its own namespace (namespaceOwnerId is the same as contributorId);
+ * namespace name is assumed to be a String form of the contributorId.
+ *
+ * It is assumed that namespaces organize contributors into groups. Or, more formally:
+ *
+ * 1) each contributor resides in a namespace
+ * 2) each namespace is owned by some contributor
+ * 3) many contributors can reside in a single namespace
+ *
+ * @param contributorId - Id of the contributor in question
+ * @return - Id of the namespace owner
+ */
+ public long getNamespaceOwnerId(long contributorId) {
+ return contributorId;
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // XXX The use of longs is strange here. It is very OSGi specific.
+ // It is likey that they are going to be replaced with some
+ // generic object in the near future.
+
+ /**
+ * Returns name of the namespace that given contributor resides in.
+ * @see getNamespaceOwnerId
+ *
+ * @param contributorId - Id of the contributor in question
+ * @return - namespace name
+ */
+ public String getNamespace(long contributorId) {
+ return String.valueOf(contributorId);
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // XXX The use of longs is strange here. It is very OSGi specific.
+ // It is likey that they are going to be replaced with some
+ // generic object in the near future.
+
+ /**
+ * Returns Ids of all contributors residing in a given namespace
+ * @see getNamespaceOwnerId
+ *
+ * @param namespace - namespace name
+ * @return - array of contributor Ids residing in the namespace
+ */
+ public long[] getNamespaceContributors(String namespace) {
+ Long theContributorId;
+ try {
+ theContributorId = Long.decode(namespace);
+ } catch (NumberFormatException e) {
+ theContributorId = new Long(-1);
+ }
+ return new long[] {theContributorId.longValue()};
+ }
+
+ /**
+ * Creates an executable extension. Override this method to supply an alternative processing
+ * for the creation of executable extensions.
+ *
+ * In this implementation registry attempts to instantiate the class specified using
+ * standard Java reflection mechanism; it assumes that constructor of such class has no arguments.
+ *
+ * @see IConfigurationElement#createExecutableExtension(String)
+ *
+ * @param contributorName - name of the extension supplier
+ * @param namespaceOwnerId - Id of the namespace owner
+ * @param namespaceName - name of the extension point namespace
+ * @param className - name of the class to be instantiated
+ * @param initData - initializer data (@see IExecutableExtension); might be null
+ * @param propertyName - name of the configuration element containing class information
+ * @param theConfElement - the configuration element containing executable extension
+ * @return - the object created; might be null
+ * @throws CoreException
+ */
+ public Object createExecutableExtension(String contributorName, long namespaceOwnerId, String namespaceName, String className, Object initData, String propertyName, IConfigurationElement theConfElement) throws CoreException {
+ Object result = null;
+ Class classInstance = null;
+ try {
+ classInstance = Class.forName(className);
+ } catch (ClassNotFoundException e1) {
+ String message = NLS.bind(RegistryMessages.exExt_findClassError, getNamespace(namespaceOwnerId), className);
+ throw new CoreException(new Status(IStatus.ERROR, RegistryMessages.OWNER_NAME, IRegistryConstants.PLUGIN_ERROR, message, e1));
+ }
+
+ try {
+ result = classInstance.newInstance();
+ } catch (Exception e) {
+ String message = NLS.bind(RegistryMessages.exExt_instantiateClassError, getNamespace(namespaceOwnerId), className);
+ throw new CoreException(new Status(IStatus.ERROR, RegistryMessages.OWNER_NAME, IRegistryConstants.PLUGIN_ERROR, message, e));
+ }
+ return result;
+ }
+
+ /**
+ * Override this method to customize scheduling of an extension registry event. Note that this method
+ * must make the following call to actually process the event:
+ *
+ * RegistryStrategy.processChangeEvent(listeners, deltas, registry);
+ *
+ * In this implementation of the method registry events are executed in a queue
+ * on a separate thread (i.e. asynchronously, sequentially).
+ *
+ * @param listeners - list of active listeners (thread safe)
+ * @param deltas - registry deltas (thread safe)
+ * @param registry - the extension registry (NOT thread safe)
+ */
+ public void scheduleChangeEvent(Object[] listeners, Map deltas, Object registry) {
+ if (registry instanceof ExtensionRegistry)
+ ((ExtensionRegistry) registry).scheduleChangeEvent(listeners, deltas);
+ }
+
+ /**
+ * This method performs actual processing of the registry change event. It should
+ * only be used by overrides of the RegistryStrategy.scheduleChangeEvent.
+ *
+ * @param listeners - the list of active listeners
+ * @param deltas - the extension registry deltas
+ * @param registry - the extension registry
+ * @return - status of the operation; null if unexpected type of the registry
+ * was encountered
+ */
+ public final static IStatus processChangeEvent(Object[] listeners, Map deltas, Object registry) {
+ if (registry instanceof ExtensionRegistry)
+ return ((ExtensionRegistry) registry).processChangeEvent(listeners, deltas);
+ return null;
+ }
+
+ /**
+ * Override this method to specify debug requirements to the registry. In this
+ * default implementation debug functionality is turned off.
+ *
+ * Note that, in general case, the extension registry plugin doesn't depend on OSGI and,
+ * therefore, can't use Eclipse .options files to discover debug options.
+ *
+ * @return true - perform debug logging and validation
+ */
+ public boolean debug() {
+ return false;
+ }
+
+ /**
+ * Override this method to specify debug requirements for the registry event processing.
+ * In this default implementation debug functionality is turned off.
+ *
+ * Note that, in general case, the extension registry plugin doesn't depend on OSGI and,
+ * therefore, can't use Eclipse .options files to discover debug options.
+ *
+ * @return true - perform debug logging and validation of the registry events
+ */
+ public boolean debugRegistryEvents() {
+ return false;
+ }
+
+ /**
+ * Specifies if the extension registry should use cache to store
+ * registry data between invocations.
+ *
+ * This basic implementation specifies that registry caching is going to tbe enabled.
+ *
+ * @return - true - should use cache; false - don't
+ */
+ public boolean cacheUse() {
+ return true;
+ }
+
+ /**
+ * Specifies if lazy cahe loading is used. If cache startegy is not supplied,
+ * lazy cache loading is used.
+ *
+ * This basic implementation specifies that lazy cache loading is going to be used.
+ *
+ * @return - true - use lazy extension registry cache loading
+ */
+ public boolean cacheLazyLoading() {
+ return true;
+ }
+
+ /**
+ * This method is called as a part of the registry cache validation. The cache is valid
+ * on the registry startup if the pair {state, time stamp} supplied by the application
+ * is the same as the {state, time stamp} saved in the registry cache.
+ *
+ * This method produces a number that corresponds to the current state of the data stored
+ * in the registry. Increment the state if registry content changed and the registry cache
+ * is no longer valid.
+ *
+ * Return 0 to indicate that state verification is not required.
+ *
+ * @return number indicating state of the application data
+ */
+ public long cacheComputeState() {
+ return 0;
+ }
+
+ /**
+ * This method is called as a part of the registry cache validation. The cache is valid
+ * on the registry startup if the pair {state, time stamp} supplied by the application
+ * is the same as the {state, time stamp} saved in the registry cache.
+ *
+ * This method calculates current time stamp for the elements stored in the extension
+ * registry. Treat this number as a hash code for the data stored in the registry.
+ * It stays the same as long as the registry content is not changing. It becomes a different
+ * number as the registry content gets modified.
+ *
+ * Return 0 to indicate that no time stamp verification is required.
+ *
+ * @return the time stamp calculated with the application data
+ */
+ public long cacheComputeTimeStamp() {
+ return 0;
+ }
+
+ /**
+ * In case if the primary cache location has no data in it, the registry
+ * attemps to get cached information from this alternative location. The cache
+ * at alternative location is always considered read-only.
+ *
+ * Return null if alternative cache location is not supported.
+ *
+ * @return - directory containing the alternative cache location
+ */
+ public File cacheAlternativeLocation() {
+ return null;
+ }
+
+ private SAXParserFactory theXMLParserFactory = null;
+
+ /**
+ * The parser used by the registry to parse descriptions of extension points
+ * and extensions from the XML input streams.
+ * @see IExtensionRegistry.addXMLContribution
+ * @return XML parser
+ */
+ public SAXParserFactory getXMLParser() {
+ if (theXMLParserFactory == null)
+ theXMLParserFactory = SAXParserFactory.newInstance();
+ return theXMLParserFactory;
+ }
+
+}
diff --git a/bundles/org.eclipse.equinox.registry/src/org/eclipse/equinox/registry/tracker/ExtensionTracker.java b/bundles/org.eclipse.equinox.registry/src/org/eclipse/equinox/registry/tracker/ExtensionTracker.java
new file mode 100644
index 000000000..7ea7c87a3
--- /dev/null
+++ b/bundles/org.eclipse.equinox.registry/src/org/eclipse/equinox/registry/tracker/ExtensionTracker.java
@@ -0,0 +1,303 @@
+/*******************************************************************************
+ * 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.equinox.registry.tracker;
+
+import java.util.HashMap;
+import java.util.Map;
+import org.eclipse.core.internal.registry.RegistryMessages;
+import org.eclipse.core.internal.registry.osgi.Activator;
+import org.eclipse.core.internal.runtime.ReferenceHashSet;
+import org.eclipse.core.internal.runtime.RuntimeLog;
+import org.eclipse.core.runtime.*;
+import org.eclipse.equinox.registry.*;
+import org.osgi.framework.BundleContext;
+import org.osgi.util.tracker.ServiceTracker;
+
+/**
+ * Implementation of the IExtensionTracker.
+ * @see org.eclipse.equinox.registry.tracker.IExtensionTracker
+ * @since 3.1
+ */
+public class ExtensionTracker implements IExtensionTracker, IRegistryChangeListener {
+ //Map keeping the association between extensions and a set of objects. Key: IExtension, value: ReferenceHashSet.
+ private Map extensionToObjects = new HashMap();
+ private ListenerList handlers = new ListenerList();
+ private final Object lock = new Object();
+ private boolean closed = false;
+ IExtensionRegistry registry; // the registry that this tacker works with
+
+ private static final Object[] EMPTY_ARRAY = new Object[0];
+
+ /**
+ * Construct a new instance of the extension tracker.
+ */
+ public ExtensionTracker() {
+ BundleContext context = Activator.getContext();
+ ServiceTracker registryTracker = new ServiceTracker(context, IExtensionRegistry.class.getName(), null);
+ registryTracker.open(true); // TODO check if argument is necessary
+ if (registryTracker != null) {
+ registry = (IExtensionRegistry) registryTracker.getService();
+ registry.addRegistryChangeListener(this);
+ registryTracker.close();
+ } else
+ RuntimeLog.log(new Status(IStatus.ERROR, RegistryMessages.OWNER_NAME, 0, RegistryMessages.registry_no_default, null));
+ }
+
+ /**
+ * Construct a new instance of the extension tracker.
+ * @param theRegistry - the extension registry containing tracked extensions and extension points.
+ */
+ public ExtensionTracker(IExtensionRegistry theRegistry) {
+ registry = theRegistry;
+ if (registry != null)
+ registry.addRegistryChangeListener(this);
+ }
+
+ /* (non-Javadoc)
+ * @see IExtensionTracker@registerHandler(IExtensionChangeHandler, IFilter)
+ */
+ public void registerHandler(IExtensionChangeHandler handler, IFilter filter) {
+ synchronized (lock) {
+ if (closed)
+ return;
+ // TODO need to store the filter with the handler
+ handlers.add(new HandlerWrapper(handler, filter));
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see IExtensionTracker@unregisterHandler(IExtensionChangeHandler)
+ */
+ public void unregisterHandler(IExtensionChangeHandler handler) {
+ synchronized (lock) {
+ if (closed)
+ return;
+ handlers.remove(new HandlerWrapper(handler, null));
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see IExtensionTracker@registerObject(IExtension, Object, int)
+ */
+ public void registerObject(IExtension element, Object object, int referenceType) {
+ if (element == null || object == null)
+ return;
+
+ synchronized (lock) {
+ if (closed)
+ return;
+
+ ReferenceHashSet associatedObjects = (ReferenceHashSet) extensionToObjects.get(element);
+ if (associatedObjects == null) {
+ associatedObjects = new ReferenceHashSet();
+ extensionToObjects.put(element, associatedObjects);
+ }
+ associatedObjects.add(object, referenceType);
+ }
+ }
+
+ /**
+ * Implementation of IRegistryChangeListener interface. This method must not
+ * be called by clients.
+ */
+ public void registryChanged(IRegistryChangeEvent event) {
+ IExtensionDelta delta[] = event.getExtensionDeltas();
+ int len = delta.length;
+ for (int i = 0; i < len; i++)
+ switch (delta[i].getKind()) {
+ case IExtensionDelta.ADDED :
+ doAdd(delta[i]);
+ break;
+ case IExtensionDelta.REMOVED :
+ doRemove(delta[i]);
+ break;
+ default :
+ break;
+ }
+ }
+
+ /**
+ * Notify all handlers whose filter matches that the given delta occured
+ * If the list of objects is not null then this is a removal and the handlers
+ * will be given a chance to process the list. If it is null then the notification is
+ * an addition.
+ * @param delta the change to broadcast
+ * @param objects the objects to pass to the handlers on removals
+ */
+ private void notify(IExtensionDelta delta, Object[] objects) {
+ // Get a copy of the handlers for safe notification
+ Object[] handlersCopy = null;
+ synchronized (lock) {
+ if (closed)
+ return;
+
+ if (handlers == null || handlers.isEmpty())
+ return;
+ handlersCopy = handlers.getListeners();
+ }
+
+ for (int i = 0; i < handlersCopy.length; i++) {
+ HandlerWrapper wrapper = (HandlerWrapper) handlersCopy[i];
+ if (wrapper.filter == null || wrapper.filter.matches(delta.getExtensionPoint())) {
+ if (objects == null)
+ applyAdd(wrapper.handler, delta.getExtension());
+ else
+ applyRemove(wrapper.handler, delta.getExtension(), objects);
+ }
+ }
+ }
+
+ protected void applyAdd(IExtensionChangeHandler handler, IExtension extension) {
+ handler.addExtension(this, extension);
+ }
+
+ private void doAdd(IExtensionDelta delta) {
+ notify(delta, null);
+ }
+
+ private void doRemove(IExtensionDelta delta) {
+ Object[] removedObjects = null;
+ synchronized (lock) {
+ if (closed)
+ return;
+
+ ReferenceHashSet associatedObjects = (ReferenceHashSet) extensionToObjects.remove(delta.getExtension());
+ if (associatedObjects == null)
+ return;
+ //Copy the objects early so we don't hold the lock too long
+ removedObjects = associatedObjects.toArray();
+ }
+ notify(delta, removedObjects);
+ }
+
+ protected void applyRemove(IExtensionChangeHandler handler, IExtension removedExtension, Object[] removedObjects) {
+ handler.removeExtension(removedExtension, removedObjects);
+ }
+
+ /* (non-Javadoc)
+ * @see IExtensionTracker@getObjects(IExtension)
+ */
+ public Object[] getObjects(IExtension element) {
+ synchronized (lock) {
+ if (closed)
+ return EMPTY_ARRAY;
+ ReferenceHashSet objectSet = (ReferenceHashSet) extensionToObjects.get(element);
+ if (objectSet == null)
+ return EMPTY_ARRAY;
+
+ return objectSet.toArray();
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see IExtensionTracker@close()
+ */
+ public void close() {
+ synchronized (lock) {
+ if (closed)
+ return;
+ if (registry != null)
+ registry.removeRegistryChangeListener(this);
+ extensionToObjects = null;
+ handlers = null;
+
+ closed = true;
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see IExtensionTracker@unregisterObject(IExtension, Object)
+ */
+ public void unregisterObject(IExtension extension, Object object) {
+ synchronized (lock) {
+ if (closed)
+ return;
+ ReferenceHashSet associatedObjects = (ReferenceHashSet) extensionToObjects.get(extension);
+ if (associatedObjects != null)
+ associatedObjects.remove(object);
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see IExtensionTracker@unregisterObject(IExtension)
+ */
+ public Object[] unregisterObject(IExtension extension) {
+ synchronized (lock) {
+ if (closed)
+ return EMPTY_ARRAY;
+ ReferenceHashSet associatedObjects = (ReferenceHashSet) extensionToObjects.remove(extension);
+ if (associatedObjects == null)
+ return EMPTY_ARRAY;
+ return associatedObjects.toArray();
+ }
+ }
+
+ /**
+ * Return an instance of filter matching all changes for the given extension point.
+ * @param xpt the extension point
+ * @return a filter
+ */
+ public static IFilter createExtensionPointFilter(final IExtensionPoint xpt) {
+ return new IFilter() {
+ public boolean matches(IExtensionPoint target) {
+ return xpt.equals(target);
+ }
+ };
+ }
+
+ /**
+ * Return an instance of filter matching all changes for the given extension points.
+ * @param xpts the extension points used to filter
+ * @return a filter
+ */
+ public static IFilter createExtensionPointFilter(final IExtensionPoint[] xpts) {
+ return new IFilter() {
+ public boolean matches(IExtensionPoint target) {
+ for (int i = 0; i < xpts.length; i++)
+ if (xpts[i].equals(target))
+ return true;
+ return false;
+ }
+ };
+ }
+
+ /**
+ * Return an instance of filter matching all changes from a given plugin.
+ * @param id the plugin id
+ * @return a filter
+ */
+ public static IFilter createNamespaceFilter(final String id) {
+ return new IFilter() {
+ public boolean matches(IExtensionPoint target) {
+ return id.equals(target.getNamespace());
+ }
+ };
+ }
+
+ private class HandlerWrapper {
+ IExtensionChangeHandler handler;
+ IFilter filter;
+
+ public HandlerWrapper(IExtensionChangeHandler handler, IFilter filter) {
+ this.handler = handler;
+ this.filter = filter;
+ }
+
+ public boolean equals(Object target) {
+ return handler.equals(((HandlerWrapper) target).handler);
+ }
+
+ public int hashCode() {
+ return handler.hashCode();
+ }
+ }
+
+}
diff --git a/bundles/org.eclipse.equinox.registry/src/org/eclipse/equinox/registry/tracker/IExtensionChangeHandler.java b/bundles/org.eclipse.equinox.registry/src/org/eclipse/equinox/registry/tracker/IExtensionChangeHandler.java
new file mode 100644
index 000000000..20d8a365a
--- /dev/null
+++ b/bundles/org.eclipse.equinox.registry/src/org/eclipse/equinox/registry/tracker/IExtensionChangeHandler.java
@@ -0,0 +1,38 @@
+/*******************************************************************************
+ * 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.equinox.registry.tracker;
+
+import org.eclipse.equinox.registry.IExtension;
+
+/**
+ * Extension change handlers are notified of changes for a given extension
+ * point in the context of an extension tracker.
+ *
+ * @since 3.1
+ */
+public interface IExtensionChangeHandler {
+
+ /**
+ * This method is called whenever an extension conforming to the extension point filter
+ * is being added to the registry.
+ * This method does not automatically register objects to the tracker.
+ * @param tracker a tracker to which the handler has been registered.
+ * @param extension the extension being added.
+ */
+ public void addExtension(IExtensionTracker tracker, IExtension extension);
+
+ /**
+ * This method is called after the removal of an extension.
+ * @param extension the extension being removed
+ * @param objects the objects that were associated with the removed extension
+ */
+ public void removeExtension(IExtension extension, Object[] objects);
+}
diff --git a/bundles/org.eclipse.equinox.registry/src/org/eclipse/equinox/registry/tracker/IExtensionTracker.java b/bundles/org.eclipse.equinox.registry/src/org/eclipse/equinox/registry/tracker/IExtensionTracker.java
new file mode 100644
index 000000000..3f543776f
--- /dev/null
+++ b/bundles/org.eclipse.equinox.registry/src/org/eclipse/equinox/registry/tracker/IExtensionTracker.java
@@ -0,0 +1,99 @@
+/*******************************************************************************
+ * 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.equinox.registry.tracker;
+
+import org.eclipse.core.internal.runtime.ReferenceHashSet;
+import org.eclipse.equinox.registry.IExtension;
+
+/**
+ * An extension tracker keeps associations between extensions and their derived objects on an extension basis.
+ * All extensions being added in a tracker will automatically be removed when the extension is uninstalled from the registry.
+ * Users interested in extension removal can register a handler that will let them know when an object is being removed.
+ * <p>
+ * This interface is not intended to be implemented by clients.
+ * </p>
+ *
+ * @since 3.1
+ */
+public interface IExtensionTracker {
+
+ /**
+ * Constant for strong (normal) reference holding.
+ *
+ * Value <code>1</code>.
+ */
+ public static final int REF_STRONG = ReferenceHashSet.HARD;
+
+ /**
+ * Constant for soft reference holding.
+ *
+ * Value <code>2</code>.
+ */
+ public static final int REF_SOFT = ReferenceHashSet.SOFT;
+
+ /**
+ * Constant for weak reference holding.
+ *
+ * Value <code>3</code>.
+ */
+ public static final int REF_WEAK = ReferenceHashSet.WEAK;
+
+ /**
+ * Register an extension change handler with this tracker using the given filter
+ * @param handler the handler to be registered
+ * @param filter the filter to use to choose interesting changes
+ */
+ public void registerHandler(IExtensionChangeHandler handler, IFilter filter);
+
+ /**
+ * Unregister the given extension change handler previously registered with this tracker
+ * @param handler the handler to be unregistered
+ */
+ public void unregisterHandler(IExtensionChangeHandler handler);
+
+ /**
+ * Create an association between the given extension and the given object.
+ * The referenceType indicates how strongly the object is being kept in memory.
+ * There is 3 possible values: strong, soft, weak.
+ * @param extension : an extension
+ * @param object : the object to associate with the extension
+ * @param referenceType : one of REF_STRONG, REF_SOFT, REF_WEAK
+ */
+ public void registerObject(IExtension extension, Object object, int referenceType);
+
+ /**
+ * Remove an association between the given extension and the given object.
+ * @param extension : the extension under which the object has been registered
+ * @param object : the object to unregister
+ */
+ public void unregisterObject(IExtension extension, Object object);
+
+ /**
+ * Remove all the objects associated with the given extension and return them.
+ * @param extension : the extension for which the objects are removed
+ * @return the objects that were associated with the extension.
+ */
+ public Object[] unregisterObject(IExtension extension);
+
+ /**
+ * Get all the objects that have been associated with the given extension.
+ * All objects registered strongly will be return unless they have been unregistered.
+ * The objects registered softly or weakly may not be returned if they have been garbage collected.
+ * @param extension the extension for which the object must be returned
+ * @return an array of objects associated with this extension, or an empty array if no association exists
+ */
+ public Object[] getObjects(IExtension extension);
+
+ /**
+ * Close the tracker. All registered objects are freed and all handlers are being automatically removed.
+ */
+ public void close();
+}
diff --git a/bundles/org.eclipse.equinox.registry/src/org/eclipse/equinox/registry/tracker/IFilter.java b/bundles/org.eclipse.equinox.registry/src/org/eclipse/equinox/registry/tracker/IFilter.java
new file mode 100644
index 000000000..fcaefd8f6
--- /dev/null
+++ b/bundles/org.eclipse.equinox.registry/src/org/eclipse/equinox/registry/tracker/IFilter.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.equinox.registry.tracker;
+
+import org.eclipse.equinox.registry.IExtensionPoint;
+
+/**
+ * A filter compares the given object to some pattern and returns true
+ * if the two match and false otherwise..
+ *
+ * The API can be implemented by clients, however factory methods are
+ * available on IExtensionTracker.
+ *
+ * @since 3.1
+ */
+public interface IFilter {
+ /**
+ * Return true if the given object matches the criteria for this filter
+ * @param target the object to match
+ * @return true if the target matches this filter, false otherwise
+ */
+ public boolean matches(IExtensionPoint target);
+}
diff --git a/bundles/org.eclipse.equinox.registry/src/org/eclipse/equinox/registry/tracker/package.html b/bundles/org.eclipse.equinox.registry/src/org/eclipse/equinox/registry/tracker/package.html
new file mode 100644
index 000000000..2ad57b474
--- /dev/null
+++ b/bundles/org.eclipse.equinox.registry/src/org/eclipse/equinox/registry/tracker/package.html
@@ -0,0 +1,14 @@
+<!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 helpers to facilitate the authoring of dynamic plug-ins.
+<h2>
+Package Specification</h2>
+This package specifies the API for tracking extensions life-cycle.
+</p>
+</body>
+</html> \ No newline at end of file

Back to the top