*** empty log message ***
diff --git a/bundles/org.eclipse.core.runtime/.options b/bundles/org.eclipse.core.runtime/.options
index 2418841..649b2a0 100644
--- a/bundles/org.eclipse.core.runtime/.options
+++ b/bundles/org.eclipse.core.runtime/.options
@@ -8,10 +8,10 @@
 org.eclipse.core.runtime/debug/context=false

 

 # Prints time taken by Plugin constructors and startup() methods.

-org.eclipse.core.runtime/timing/startup=false

+org.eclipse.core.runtime/debug/startup=false

 

 # Prints time taken by Plugin.shutdown() methods.

-org.eclipse.core.runtime/timing/shutdown=false

+org.eclipse.core.runtime/debug/shutdown=false

 

 # Turn on debugging for the registry.

 org.eclipse.core.runtime/registry/debug=false

diff --git a/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/plugins/PluginRegistry.java b/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/plugins/PluginRegistry.java
index 54fb8df..971db47 100644
--- a/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/plugins/PluginRegistry.java
+++ b/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/plugins/PluginRegistry.java
@@ -191,9 +191,11 @@
 }

 public void saveRegistry() throws IOException {

 	IPath path = InternalPlatform.getMetaArea().getRegistryPath();

+	IPath tempPath = InternalPlatform.getMetaArea().getBackupFilePathFor(path);

+

 	DataOutputStream output = null;

 	try {

-		output = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(path.toFile())));

+		output = new DataOutputStream(new BufferedOutputStream(new SafeFileOutputStream(path.toOSString(),tempPath.toOSString())));

 	} catch (IOException ioe) {

 		String message = Policy.bind("meta.unableToCreateCache");

 		IStatus status = new Status(IStatus.ERROR, Platform.PI_RUNTIME, Platform.PLUGIN_ERROR, message, ioe);

diff --git a/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/plugins/RegistryLoader.java b/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/plugins/RegistryLoader.java
index b5f9a02..827645a 100644
--- a/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/plugins/RegistryLoader.java
+++ b/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/plugins/RegistryLoader.java
@@ -8,6 +8,7 @@
 import java.io.*;

 import java.net.MalformedURLException;

 import java.net.URL;

+import java.util.Properties;

 

 import org.eclipse.core.internal.runtime.InternalPlatform;

 import org.eclipse.core.internal.runtime.Policy;

@@ -124,6 +125,7 @@
 	PluginModel entry = processManifestFile(location);

 	if (entry == null)

 		return false;

+	entry.setVersion(getQualifiedVersion(entry, location)); // check for version qualifier

 	if (entry instanceof PluginDescriptorModel) {

 		if (entry.getId() == null || entry.getVersion() == null) {

 			return parseProblem(Policy.bind("parse.nullPluginIdentifier", location.toString()));

@@ -150,4 +152,33 @@
 	InternalPlatform.addLastModifiedTime(location.getFile(), new File(location.getFile()).lastModified());

 	return true;

 }

+private String getQualifiedVersion(PluginModel entry, URL base) {

+	if (entry == null || entry.getVersion() == null || entry.getId() == null)

+		return null;

+		

+	InputStream is = null;

+	try {		

+		// check to see if we have buildmanifest.properties for this plugin

+		URL manifest = null;

+		manifest = new URL(base, "buildmanifest.properties");

+		Properties props = new Properties();

+		is = manifest.openStream();

+		props.load(is);

+	

+		// lookup qualifier for this plugin and "morph" the identifier if needed

+		String key = "plugin@"+entry.getId();

+		String qualifier = props.getProperty(key);

+		if (qualifier == null)

+			return entry.getVersion();

+		PluginVersionIdentifier v = new PluginVersionIdentifier(entry.getVersion());

+		if (!v.getQualifierComponent().equals(""))

+			return entry.getVersion();

+		else 

+			return (new PluginVersionIdentifier(v.getMajorComponent(), v.getMinorComponent(), v.getServiceComponent(), qualifier)).toString();

+	} catch(Exception e) {

+		return entry.getVersion();

+	} finally {		

+		if (is != null) try { is.close(); } catch(IOException e) {}

+	}

+}

 }

diff --git a/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/runtime/InternalPlatform.java b/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/runtime/InternalPlatform.java
index 9874ddc..9b68aa0 100644
--- a/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/runtime/InternalPlatform.java
+++ b/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/runtime/InternalPlatform.java
@@ -62,8 +62,8 @@
 	// execution options

 	private static final String OPTION_DEBUG = Platform.PI_RUNTIME + "/debug";

 	private static final String OPTION_DEBUG_SYSTEM_CONTEXT = Platform.PI_RUNTIME + "/debug/context";

-	private static final String OPTION_DEBUG_STARTUP = Platform.PI_RUNTIME + "/timing/startup";

-	private static final String OPTION_DEBUG_SHUTDOWN = Platform.PI_RUNTIME + "/timing/shutdown";

+	private static final String OPTION_DEBUG_STARTUP = Platform.PI_RUNTIME + "/debug/startup";

+	private static final String OPTION_DEBUG_SHUTDOWN = Platform.PI_RUNTIME + "/debug/shutdown";

 	private static final String OPTION_DEBUG_PLUGINS = Platform.PI_RUNTIME + "/registry/debug";

 	private static final String OPTION_DEBUG_PLUGINS_DUMP = Platform.PI_RUNTIME + "/registry/debug/dump";

 

@@ -645,6 +645,7 @@
 	InternalFactory factory = new InternalFactory(problems);

 

 	IPath path = getMetaArea().getRegistryPath();

+	IPath tempPath = getMetaArea().getBackupFilePathFor(path);

 	DataInputStream input = null;

 	registry = null;

 	// augment the plugin path with any additional platform entries

@@ -652,7 +653,7 @@
 	URL[] augmentedPluginPath = getAugmentedPluginPath(pluginPath);

 	if (path.toFile().exists() && cacheRegistry) {

 		try {

-			input = new DataInputStream(new BufferedInputStream(new FileInputStream(path.toFile())));

+			input = new DataInputStream(new BufferedInputStream(new SafeFileInputStream(path.toOSString(), tempPath.toOSString())));

 			try {

 				long start = System.currentTimeMillis();

 				RegistryCacheReader cacheReader = new RegistryCacheReader(factory);

diff --git a/bundles/org.eclipse.core.runtime/src/org/eclipse/core/runtime/Plugin.java b/bundles/org.eclipse.core.runtime/src/org/eclipse/core/runtime/Plugin.java
index d207fdf..25c3d71 100644
--- a/bundles/org.eclipse.core.runtime/src/org/eclipse/core/runtime/Plugin.java
+++ b/bundles/org.eclipse.core.runtime/src/org/eclipse/core/runtime/Plugin.java
@@ -13,6 +13,7 @@
 import org.eclipse.core.internal.boot.PlatformURLHandler;

 import java.util.StringTokenizer;

 import java.util.Vector;

+import java.util.Map;

 import java.net.*;

 import java.io.*;

 

@@ -199,6 +200,22 @@
  * @return a URL for the given path or <code>null</code>

  */

 public final URL find(IPath path) {

+	return find(path, null);

+}

+/**

+ * Returns a URL for the given path.  Returns <code>null</code> if the URL

+ * could not be computed or created.

+ * 

+ * @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 (without the $ characters, 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>

+ */

+public final URL find(IPath path, Map override) {

 	URL install = getDescriptor().getInstallURL();

 	String first = path.segment(0);

 	if (first.charAt(0) != '$') {		

@@ -209,34 +226,57 @@
 	}

 	IPath rest = path.removeFirstSegments(1);

 	if (first.equalsIgnoreCase("$nl$"))

-		return findNL(install, rest);

+		return findNL(install, rest, override);

 	if (first.equalsIgnoreCase("$os$"))

-		return findOS(install, rest);

+		return findOS(install, rest, override);

 	if (first.equalsIgnoreCase("$ws$"))

-		return findWS(install, rest);

+		return findWS(install, rest, override);

 	if (first.equalsIgnoreCase("$files$"))

 		return null;

 	return null;

 }

 

-private URL findOS(URL install, IPath path) {

-	String filePath = "os/" + BootLoader.getOS() + "/" + path.toString();	

+private URL findOS(URL install, IPath path, Map override) {

+	String os = null;

+	if (override != null)

+		try { // check for override

+			os = (String) override.get("os");

+		} catch (ClassCastException e) {

+		}

+	if (os == null)

+		os = BootLoader.getOS(); // use default

+	String filePath = "os/" + os + "/" + path.toString();	

 	URL result = findInPlugin(install, filePath);

 	if (result != null)

 		return result;	

 	return findInFragments(filePath);

 }

 

-private URL findWS(URL install, IPath path) {

-	String filePath = "ws/" + BootLoader.getWS() + "/" + path.toString();	

+private URL findWS(URL install, IPath path, Map override) {

+	String ws = null;

+	if (override != null)

+		try { // check for override

+			ws = (String) override.get("ws");

+		} catch (ClassCastException e) {

+		}

+	if (ws == null)

+		ws = BootLoader.getWS(); // use default

+	String filePath = "ws/" + ws + "/" + path.toString();	

 	URL result = findInPlugin(install, filePath);

 	if (result != null)

 		return result;	

 	return findInFragments(filePath);

 }

 

-private URL findNL(URL install, IPath path) {

-	String nl = BootLoader.getNL();

+private URL findNL(URL install, IPath path, Map override) {

+	String nl = null;

+	if (override != null)

+		try { // check for override

+			nl = (String) override.get("nl");

+		} catch (ClassCastException e) {

+		}

+	if (nl == null)

+		nl = BootLoader.getNL(); // use default

 	URL result = null;

 	boolean done = false;

 	

diff --git a/bundles/org.eclipse.core.runtime/src/org/eclipse/core/runtime/PluginVersionIdentifier.java b/bundles/org.eclipse.core.runtime/src/org/eclipse/core/runtime/PluginVersionIdentifier.java
index 2244a93..3a3d2c3 100644
--- a/bundles/org.eclipse.core.runtime/src/org/eclipse/core/runtime/PluginVersionIdentifier.java
+++ b/bundles/org.eclipse.core.runtime/src/org/eclipse/core/runtime/PluginVersionIdentifier.java
@@ -12,25 +12,29 @@
 /**

  * <p>

  * Version identifier for a plug-in. In its string representation, 

- * it consists of up to 3 positive integer numbers separated by decimal point.

+ * 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</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

- * and service level component. 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 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,

@@ -40,12 +44,14 @@
  * Clients may instantiate; not intended to be subclassed by clients.

  * </p>

  * @see IPluginDescriptor#getVersionIdentifier

+ * @see java.lang.String#compareTo 

  */	

 public final class PluginVersionIdentifier {

 		

-	private	int major = 0;

+	private int major = 0;

 	private int minor = 0;

 	private int service = 0;

+	private String qualifier = "";

 	

 	private static final String	SEPARATOR = ".";

 /**

@@ -56,31 +62,46 @@
  * @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) {

 

 	Assert.isTrue(major>=0);

 	Assert.isTrue(minor>=0);

 	Assert.isTrue(service>=0);

+	if (qualifier == null) qualifier = "";

 	

 	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 represenation consists of up to 3 integer 

- * numbers separated by decimal point.

+ * The string represenation 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</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

+ * @param versionId string representation of the version identifier. 

+ * Qualifier characters that are not a letter or a digit are replaced.

  */

 public PluginVersionIdentifier(String versionId) {

 

@@ -92,22 +113,23 @@
 	Assert.isTrue(s.indexOf(SEPARATOR+SEPARATOR)==-1);

 	

 	StringTokenizer st = new StringTokenizer(s, SEPARATOR);

-	Integer token;

-	Vector elements = new Vector(3);

+	Vector elements = new Vector(4);

 

 	while(st.hasMoreTokens()) {

-		token = new Integer((String)st.nextToken());

-		Assert.isTrue(token.intValue() >= 0);

-		elements.addElement(token);

+		elements.addElement(st.nextToken());

 	}

 

 	Assert.isTrue(elements.size()>0);

-	Assert.isTrue(elements.size()<=3);

+	Assert.isTrue(elements.size()<=4);

 

-	if (elements.size()>=1) this.major = ((Integer)elements.elementAt(0)).intValue();

-	if (elements.size()>=2) this.minor = ((Integer)elements.elementAt(1)).intValue();

-	if (elements.size()>=3) this.service = ((Integer)elements.elementAt(2)).intValue();

-

+	if (elements.size()>=1) this.major = (new Integer((String)elements.elementAt(0))).intValue();

+	if (elements.size()>=2) this.minor = (new Integer((String)elements.elementAt(1))).intValue();

+	if (elements.size()>=3) this.service = (new Integer((String)elements.elementAt(2))).intValue();

+	if (elements.size()>=4) this.qualifier = verifyQualifier((String)elements.elementAt(3));

+	

+	Assert.isTrue(this.major >= 0);

+	Assert.isTrue(this.minor >= 0);

+	Assert.isTrue(this.service >= 0);

 }

 /**

  * Compare version identifiers for equality. Identifiers are

@@ -120,7 +142,7 @@
 	if (!(object instanceof PluginVersionIdentifier))

 		return false;

 	PluginVersionIdentifier v = (PluginVersionIdentifier) object;

-	return v.getMajorComponent() == major && v.getMinorComponent() == minor && v.getServiceComponent() == service;

+	return v.getMajorComponent() == major && v.getMinorComponent() == minor && v.getServiceComponent() == service && v.getQualifierComponent().equals(qualifier);

 }

 /**

  * Returns a hash code value for the object. 

@@ -128,7 +150,11 @@
  * @return an integer which is a hash code value for this object.

  */

 public int hashCode() {

-	return major + minor + service;

+	int code = major + minor + service; // R1.0 result

+	if (qualifier.equals(""))

+		return code;

+	else

+		return code + qualifier.hashCode();

 }

 /**

  * Returns the major (incompatible) component of this 

@@ -158,16 +184,27 @@
 	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 or equal to the argument minor component, or the

+ * 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 all components are

- * equal.

+ * greater than the argument service component, or the major, minor and

+ * service components are equal and the qualifier component is

+ * greated than the argument qualifier component (using lexicographic

+ * string comparison), or all components are equal.

  * </p>

  *

  * @param versionId the other version identifier

@@ -186,7 +223,12 @@
 		return true;

 	if ((major == id.getMajorComponent()) &&

 	    (minor == id.getMinorComponent()) &&

-	    (service >= id.getServiceComponent()))

+	    (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;

@@ -199,7 +241,10 @@
  * 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.

+ * of the argument identifier. If the service levels are equal, the two 

+ * version identifiers are considered to be equivalent if this qualifier is 

+ * greated or equal to the qualifier of the argument (using lexicographic

+ * string comparison).

  * </p>

  *

  * @param versionId the other version identifier

@@ -216,7 +261,11 @@
 		return true;

 	if (minor < id.getMinorComponent())

 		return false;

-	if (service >= id.getServiceComponent())

+	if (service > id.getServiceComponent())

+		return true;

+	if (service < id.getServiceComponent())

+		return false;

+	if (qualifier.compareTo(id.getQualifierComponent())>=0)

 		return true;

 	else

 		return false;

@@ -226,7 +275,11 @@
  * <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.

+ * as the argument. If the service levels are equal, the two version

+ * identifiers are considered to be equivalent if this qualifier is 

+ * greated or equal to the qualifier of the argument (using lexicographic

+ * string comparison).

+ * 

  * </p>

  *

  * @param versionId the other version identifier

@@ -241,7 +294,11 @@
 		return false;

 	if (minor != id.getMinorComponent())

 		return false;

-	if (service >= id.getServiceComponent())

+	if (service > id.getServiceComponent())

+		return true;

+	if (service < id.getServiceComponent())

+		return false;

+	if (qualifier.compareTo(id.getQualifierComponent())>=0)

 		return true;

 	else

 		return false;

@@ -250,7 +307,7 @@
  * Compares two version identifiers for perfect equality.

  * <p>

  * Two version identifiers are considered to be perfectly equal if their

- * major, minor and service components are equal

+ * major, minor, service and qualifier components are equal

  * </p>

  *

  * @param versionId the other version identifier

@@ -264,7 +321,8 @@
 		return false;

 	if ( (major != id.getMajorComponent()) ||

 	     (minor != id.getMinorComponent()) ||

-         (service != id.getServiceComponent()) )

+         (service != id.getServiceComponent()) ||

+         (!qualifier.equals(id.getQualifierComponent())) )

 		return false;

 	else

 		return true;

@@ -281,7 +339,7 @@
 public boolean isGreaterThan(PluginVersionIdentifier id) {

 

 	if (id == null) {

-		if (major==0 && minor==0 && service==0) return false;

+		if (major==0 && minor==0 && service==0 && qualifier.equals("")) return false;

 		else return true;

 	}

 

@@ -289,7 +347,9 @@
 	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 true;	

+	if (service < id.getServiceComponent()) return false;

+	if (qualifier.compareTo(id.getQualifierComponent())>0) return true;

 	else return false;

 

 }

@@ -301,6 +361,22 @@
  * @return the string representation of this plug-in version identifier

  */

 public String toString() {

-	return major+SEPARATOR+minor+SEPARATOR+service;

+	String base = major+SEPARATOR+minor+SEPARATOR+service; // R1.0 result

+	if (qualifier.equals(""))

+		return base;

+	else

+		return base + SEPARATOR + qualifier;

+}

+

+private 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;

 }

 }