*** empty log message ***
diff --git a/update/org.eclipse.update.ui/src/org/eclipse/update/internal/ui/UpdateUIPlugin.java b/update/org.eclipse.update.ui/src/org/eclipse/update/internal/ui/UpdateUIPlugin.java
index 3353a6f..59aafa6 100644
--- a/update/org.eclipse.update.ui/src/org/eclipse/update/internal/ui/UpdateUIPlugin.java
+++ b/update/org.eclipse.update.ui/src/org/eclipse/update/internal/ui/UpdateUIPlugin.java
@@ -252,6 +252,26 @@
 		}

 		return (IFeature[]) features.toArray(new IFeature[features.size()]);

 	}

+	

+	public static boolean isPatch(

+		IFeature target,

+		IFeature candidate) {

+		VersionedIdentifier vid = target.getVersionedIdentifier();

+		IImport[] imports = candidate.getImports();

+		IImport reference = null;

+		for (int i = 0; i < imports.length; i++) {

+			IImport iimport = imports[i];

+			if (iimport.isPatch()) {

+				VersionedIdentifier ivid = iimport.getVersionedIdentifier();

+				if (vid.equals(ivid)) {

+					// Bingo.

+					return true;

+				}

+			}

+		}

+		return false;

+	}

+

 	/**

 	 * Gets the database.

 	 * @return Returns a AuthorizationDatabase

diff --git a/update/org.eclipse.update.ui/src/org/eclipse/update/internal/ui/UpdateUIPluginResources.properties b/update/org.eclipse.update.ui/src/org/eclipse/update/internal/ui/UpdateUIPluginResources.properties
index 1a7b122..fbfac0e 100644
--- a/update/org.eclipse.update.ui/src/org/eclipse/update/internal/ui/UpdateUIPluginResources.properties
+++ b/update/org.eclipse.update.ui/src/org/eclipse/update/internal/ui/UpdateUIPluginResources.properties
@@ -111,11 +111,13 @@
 ActivityConstraints.rootMessageInitial = Current configuration contains errors that are not corrected by the requested operation. See details for more information.

 ActivityConstraints.platform = Resulting configuration does not contain the platform.

 ActivityConstraints.primary = Resulting configuration does not contain the primary feature.

-ActivityConstraints.prereq = Feature requires plug-in "{0}".

-ActivityConstraints.prereqPerfect = Feature requires plug-in "{0} ({1})".

-ActivityConstraints.prereqEquivalent = Feature requires plug-in "{0} ({1})", or equivalent.

-ActivityConstraints.prereqCompatible = Feature requires plug-in "{0} ({1})", or compatible.

-ActivityConstraints.prereqGreaterOrEqual = Feature requires plug-in "{0} ({1})", or later version.

+ActivityConstaints.prereq.plugin = plug-in

+ActivityConstaints.prereq.feature = feature

+ActivityConstraints.prereq = Feature requires {0} "{1}".

+ActivityConstraints.prereqPerfect = Feature requires {0} "{1} ({2})".

+ActivityConstraints.prereqEquivalent = Feature requires {0} "{1} ({2})", or equivalent.

+ActivityConstraints.prereqCompatible = Feature requires {0} "{1} ({2})", or compatible.

+ActivityConstraints.prereqGreaterOrEqual = Feature requires {0} "{1} ({2})", or later version.

 ActivityConstraints.os = Feature operating system does not match current environment.

 ActivityConstraints.ws = Feature windowing system does not match current environment.

 ActivityConstraints.arch = Feature platform architecture does not match current environment.

diff --git a/update/org.eclipse.update.ui/src/org/eclipse/update/internal/ui/forms/ActivityConstraints.java b/update/org.eclipse.update.ui/src/org/eclipse/update/internal/ui/forms/ActivityConstraints.java
index 7da4fe8..4d16330 100644
--- a/update/org.eclipse.update.ui/src/org/eclipse/update/internal/ui/forms/ActivityConstraints.java
+++ b/update/org.eclipse.update.ui/src/org/eclipse/update/internal/ui/forms/ActivityConstraints.java
@@ -25,6 +25,10 @@
 	private static final String KEY_WS = "ActivityConstraints.ws";
 	private static final String KEY_ARCH = "ActivityConstraints.arch";
 	private static final String KEY_PREREQ = "ActivityConstraints.prereq";
+	private static final String KEY_PREREQ_PLUGIN =
+		"ActivityConstaints.prereq.plugin";
+	private static final String KEY_PREREQ_FEATURE =
+		"ActivityConstaints.prereq.feature";
 	private static final String KEY_PREREQ_PERFECT =
 		"ActivityConstraints.prereqPerfect";
 	private static final String KEY_PREREQ_EQUIVALENT =
@@ -33,7 +37,7 @@
 		"ActivityConstraints.prereqCompatible";
 	private static final String KEY_PREREQ_GREATER =
 		"ActivityConstraints.prereqGreaterOrEqual";
-	private static final String KEY_OPTIONAL_CHILD = 
+	private static final String KEY_OPTIONAL_CHILD =
 		"ActivityConstraints.optionalChild";
 	private static final String KEY_CYCLE = "ActivityConstraints.cycle";
 	private static final String KEY_CONFLICT = "ActivityConstraints.conflict";
@@ -192,6 +196,8 @@
 		ArrayList status) {
 		try {
 			ArrayList features = computeFeatures();
+			if (oldFeature == null)
+				checkPatch(newFeature, features, status);
 			features =
 				computeFeaturesAfterOperation(features, newFeature, oldFeature);
 			checkConstraints(features, status);
@@ -290,7 +296,8 @@
 	/*
 	 * Compute a list of configured features
 	 */
-	private static ArrayList computeFeatures(boolean configuredOnly) throws CoreException {
+	private static ArrayList computeFeatures(boolean configuredOnly)
+		throws CoreException {
 		ArrayList features = new ArrayList();
 		ILocalSite localSite = SiteManager.getLocalSite();
 		IInstallConfiguration config = localSite.getCurrentConfiguration();
@@ -298,9 +305,9 @@
 
 		for (int i = 0; i < csites.length; i++) {
 			IConfiguredSite csite = csites[i];
-			
+
 			IFeatureReference[] crefs;
-			
+
 			if (configuredOnly)
 				crefs = csite.getConfiguredFeatures();
 			else
@@ -383,6 +390,11 @@
 				null,
 				true /* tolerate missing children */
 		);
+		if (remove!=null) {
+			// Patches to features are removed together with
+			// those features. Include them in the list.
+			contributePatchesFor(remove, features, removeTree);
+		}
 
 		if (add != null)
 			features.addAll(addTree);
@@ -393,6 +405,21 @@
 		return features;
 	}
 
+	private static void contributePatchesFor(
+		IFeature feature,
+		ArrayList features,
+		ArrayList result)
+		throws CoreException {
+		for (int i = 0; i < features.size(); i++) {
+			IFeature candidate = (IFeature) features.get(i);
+			if (UpdateUIPlugin.isPatch(feature, candidate)) {
+				ArrayList removeTree =
+					computeFeatureSubtree(candidate, null, null, true);
+				result.addAll(removeTree);
+			}
+		}
+	}
+
 	/*
 	 * Compute a list of features that will be configured after performing the revert
 	 */
@@ -501,6 +528,68 @@
 	}
 
 	/*
+	 * 
+	 */
+	private static void checkPatch(
+		IFeature feature,
+		ArrayList features,
+		ArrayList status)
+		throws CoreException {
+		IImport[] imports = feature.getImports();
+		for (int i = 0; i < imports.length; i++) {
+			IImport iimport = imports[i];
+			if (iimport.isPatch()) {
+				// A patch - check unique
+				checkUnique(feature, features, status);
+				return;
+			}
+		}
+	}
+
+	/*
+	 * 
+	 */
+	private static void checkUnique(
+		IFeature feature,
+		ArrayList features,
+		ArrayList status)
+		throws CoreException {
+		if (features == null)
+			return;
+		IFeatureReference[] irefs = feature.getIncludedFeatureReferences();
+		for (int i = 0; i < irefs.length; i++) {
+			IFeatureReference iref = irefs[i];
+			IFeature ifeature = iref.getFeature();
+			VersionedIdentifier vid = ifeature.getVersionedIdentifier();
+			String id = vid.getIdentifier();
+			PluginVersionIdentifier version = vid.getVersion();
+			for (int j = 0; j < features.size(); j++) {
+				IFeature candidate = (IFeature) features.get(j);
+				VersionedIdentifier cvid = candidate.getVersionedIdentifier();
+				String cid = cvid.getIdentifier();
+				PluginVersionIdentifier cversion = cvid.getVersion();
+				if (cid.equals(id)) {
+					// The same identifier - this one will
+					// be unconfigured. Check if it is lower,
+					// otherwise flag.
+					if (!version.isGreaterThan(cversion)) {
+						// Don't allow this.
+						String msg =
+							"Included feature \""
+								+ ifeature.getLabel()
+								+ "("
+								+ version.toString()
+								+ ")\" is older than the currently active feature.";
+						status.add(createStatus(feature, msg));
+
+					}
+				}
+			}
+			checkUnique(ifeature, features, status);
+		}
+	}
+
+	/*
 	 * validate constraints
 	 */
 	private static void checkConstraints(ArrayList features, ArrayList status)
@@ -635,10 +724,12 @@
 
 			for (int j = 0; j < imports.length; j++) {
 				IImport iimport = imports[j];
-				// for each import determine plugin, version, match we need
+				// for each import determine plugin or feature, version, match we need
 				VersionedIdentifier iid = iimport.getVersionedIdentifier();
 				String id = iid.getIdentifier();
 				PluginVersionIdentifier version = iid.getVersion();
+				boolean featurePrereq =
+					iimport.getKind() == IImport.KIND_FEATURE;
 				boolean ignoreVersion =
 					version.getMajorComponent() == 0
 						&& version.getMinorComponent() == 0
@@ -648,65 +739,99 @@
 					rule = IImport.RULE_COMPATIBLE;
 
 				boolean found = false;
-				for (int k = 0; k < plugins.size(); k++) {
-					// see if we have a plugin that matches
-					IPluginEntry plugin = (IPluginEntry) plugins.get(k);
-					VersionedIdentifier pid = plugin.getVersionedIdentifier();
-					PluginVersionIdentifier pversion = pid.getVersion();
-					if (id.equals(pid.getIdentifier())) {
+
+				ArrayList candidates;
+
+				if (featurePrereq)
+					candidates = features;
+				else
+					candidates = plugins;
+				for (int k = 0; k < candidates.size(); k++) {
+					VersionedIdentifier cid;
+					if (featurePrereq) {
+						// the candidate is a feature
+						IFeature candidate = (IFeature) candidates.get(k);
+						// skip self
+						if (feature.equals(candidate))
+							continue;
+						cid = candidate.getVersionedIdentifier();
+					} else {
+						// the candidate is a plug-in
+						IPluginEntry plugin = (IPluginEntry) candidates.get(k);
+						cid = plugin.getVersionedIdentifier();
+					}
+					PluginVersionIdentifier cversion = cid.getVersion();
+					if (id.equals(cid.getIdentifier())) {
 						// have a candidate
 						if (ignoreVersion)
 							found = true;
 						else if (
 							rule == IImport.RULE_PERFECT
-								&& pversion.isPerfect(version))
+								&& cversion.isPerfect(version))
 							found = true;
 						else if (
 							rule == IImport.RULE_EQUIVALENT
-								&& pversion.isEquivalentTo(version))
+								&& cversion.isEquivalentTo(version))
 							found = true;
 						else if (
 							rule == IImport.RULE_COMPATIBLE
-								&& pversion.isCompatibleWith(version))
+								&& cversion.isCompatibleWith(version))
 							found = true;
 						else if (
 							rule == IImport.RULE_GREATER_OR_EQUAL
-								&& pversion.isGreaterOrEqualTo(version))
+								&& cversion.isGreaterOrEqualTo(version))
 							found = true;
 					}
 					if (found)
 						break;
 				}
+
 				if (!found) {
 					// report status
+					String target =
+						featurePrereq
+							? UpdateUIPlugin.getResourceString(
+								KEY_PREREQ_FEATURE)
+							: UpdateUIPlugin.getResourceString(KEY_PREREQ_PLUGIN);
 					String msg =
 						UpdateUIPlugin.getFormattedMessage(
 							KEY_PREREQ,
-							new String[] { id });
+							new String[] { target, id });
 
 					if (!ignoreVersion) {
 						if (rule == IImport.RULE_PERFECT)
 							msg =
 								UpdateUIPlugin.getFormattedMessage(
 									KEY_PREREQ_PERFECT,
-									new String[] { id, version.toString()});
+									new String[] {
+										target,
+										id,
+										version.toString()});
 						else if (rule == IImport.RULE_EQUIVALENT)
 							msg =
 								UpdateUIPlugin.getFormattedMessage(
 									KEY_PREREQ_EQUIVALENT,
-									new String[] { id, version.toString()});
+									new String[] {
+										target,
+										id,
+										version.toString()});
 						else if (rule == IImport.RULE_COMPATIBLE)
 							msg =
 								UpdateUIPlugin.getFormattedMessage(
 									KEY_PREREQ_COMPATIBLE,
-									new String[] { id, version.toString()});
+									new String[] {
+										target,
+										id,
+										version.toString()});
 						else if (rule == IImport.RULE_GREATER_OR_EQUAL)
 							msg =
 								UpdateUIPlugin.getFormattedMessage(
 									KEY_PREREQ_GREATER,
-									new String[] { id, version.toString()});
+									new String[] {
+										target,
+										id,
+										version.toString()});
 					}
-
 					status.add(createStatus(feature, msg));
 				}
 			}
@@ -734,28 +859,30 @@
 			}
 		}
 	}
-	
+
 	/*
 	 * Verify that a parent of an optional child is configured
 	 * before we allow the child to be configured as well
 	 */
-	
-	private static void checkOptionalChildConfiguring(IFeature feature, ArrayList status) throws CoreException {
+
+	private static void checkOptionalChildConfiguring(
+		IFeature feature,
+		ArrayList status)
+		throws CoreException {
 		ILocalSite localSite = SiteManager.getLocalSite();
 		IInstallConfiguration config = localSite.getCurrentConfiguration();
 		IConfiguredSite[] csites = config.getConfiguredSites();
 
-		boolean included=false;
+		boolean included = false;
 		for (int i = 0; i < csites.length; i++) {
 			IConfiguredSite csite = csites[i];
 			IFeatureReference[] crefs = csite.getSite().getFeatureReferences();
 			for (int j = 0; j < crefs.length; j++) {
 				IFeatureReference cref = crefs[j];
-				IFeature cfeature=null;
+				IFeature cfeature = null;
 				try {
 					cfeature = cref.getFeature();
-				}
-				catch (CoreException e) {
+				} catch (CoreException e) {
 					// Ignore missing optional feature.
 					if (cref.isOptional())
 						continue;
@@ -764,7 +891,7 @@
 				}
 				if (isParent(cfeature, feature, true)) {
 					// Included in at least one feature as optional
-					included=true;
+					included = true;
 					if (csite.isConfigured(cfeature)) {
 						// At least one feature parent
 						// is enabled - it is OK to
@@ -779,21 +906,23 @@
 			// no parent is currently configured.
 			String msg = UpdateUIPlugin.getResourceString(KEY_OPTIONAL_CHILD);
 			status.add(createStatus(feature, msg));
-		}
-		else {
+		} else {
 			//feature is root - can be configured
 		}
 	}
-	
-	private static boolean isParent(IFeature candidate, IFeature feature, boolean optionalOnly) throws CoreException {
-		IFeatureReference [] refs = candidate.getIncludedFeatureReferences();
-		for (int i=0; i<refs.length; i++) {
+
+	private static boolean isParent(
+		IFeature candidate,
+		IFeature feature,
+		boolean optionalOnly)
+		throws CoreException {
+		IFeatureReference[] refs = candidate.getIncludedFeatureReferences();
+		for (int i = 0; i < refs.length; i++) {
 			IFeatureReference child = refs[i];
 			VersionedIdentifier cvid;
 			try {
 				cvid = child.getVersionedIdentifier();
-			}
-			catch (CoreException e) {
+			} catch (CoreException e) {
 				// Ignore missing optional children
 				if (child.isOptional())
 					continue;
@@ -803,7 +932,7 @@
 			if (feature.getVersionedIdentifier().equals(cvid)) {
 				// included; return true if optionality is not 
 				// important or it is and the inclusion is optional
-				return optionalOnly==false || child.isOptional();
+				return optionalOnly == false || child.isOptional();
 			}
 		}
 		return false;
@@ -847,12 +976,13 @@
 			fullMessage,
 			null);
 	}
-	
+
 	private static ArrayList createList(String commaSeparatedList) {
 		ArrayList list = new ArrayList();
 		if (commaSeparatedList != null) {
-			StringTokenizer t = new StringTokenizer(commaSeparatedList.trim(), ",");
-			while(t.hasMoreTokens()) {
+			StringTokenizer t =
+				new StringTokenizer(commaSeparatedList.trim(), ",");
+			while (t.hasMoreTokens()) {
 				String token = t.nextToken().trim();
 				if (!token.equals(""))
 					list.add(token);
diff --git a/update/org.eclipse.update.ui/src/org/eclipse/update/internal/ui/wizards/InstallWizard.java b/update/org.eclipse.update.ui/src/org/eclipse/update/internal/ui/wizards/InstallWizard.java
index cff9f9c..a288352 100644
--- a/update/org.eclipse.update.ui/src/org/eclipse/update/internal/ui/wizards/InstallWizard.java
+++ b/update/org.eclipse.update.ui/src/org/eclipse/update/internal/ui/wizards/InstallWizard.java
@@ -185,6 +185,9 @@
 						throwError(UpdateUIPlugin.getResourceString(KEY_OLD));

 				}

 			}

+			if (oldFeature == null) {

+				ensureUnique(config, feature, targetSite);

+			}

 		} else if (job.getJobType() == PendingChange.CONFIGURE) {

 			configure(job.getFeature());

 		} else if (job.getJobType() == PendingChange.UNCONFIGURE) {

@@ -197,6 +200,35 @@
 		model.addPendingChange(job);

 	}

 

+	static void ensureUnique(

+		IInstallConfiguration config,

+		IFeature feature,

+		IConfiguredSite targetSite)

+		throws CoreException {

+		boolean patch = false;

+		IImport[] imports = feature.getImports();

+		for (int i = 0; i < imports.length; i++) {

+			IImport iimport = imports[i];

+			if (iimport.isPatch()) {

+				patch = true;

+				break;

+			}

+		}

+		// Only need to check features that patch other features.

+		if (!patch)

+			return;

+		IFeature localFeature = findLocalFeature(targetSite, feature);

+		ArrayList oldFeatures = new ArrayList();

+		// First collect all older active features that

+		// have the same ID as new features marked as 'unique'.

+		collectOldFeatures(localFeature, targetSite, oldFeatures);

+		// Now unconfigure old features to enforce uniqueness

+		for (int i = 0; i < oldFeatures.size(); i++) {

+			IFeature oldFeature = (IFeature) oldFeatures.get(i);

+			unconfigure(config, oldFeature);

+		}

+	}

+

 	private void throwError(String message) throws CoreException {

 		IStatus status =

 			new Status(

@@ -228,10 +260,33 @@
 		throws CoreException {

 		IConfiguredSite site = findConfigSite(feature, config);

 		if (site != null) {

-			return site.unconfigure(feature);

+			boolean result = site.unconfigure(feature);

+			if (!result) return false;

+			return unconfigurePatches(site, feature);

 		}

 		return false;

 	}

+	

+	static boolean unconfigurePatches(IConfiguredSite site, IFeature feature) {

+		IFeatureReference [] refs = site.getFeatureReferences();

+		boolean totalResult = true;

+		for (int i=0; i<refs.length; i++) {

+			IFeatureReference ref = refs[i];

+			try {

+				IFeature candidate = ref.getFeature();

+				if (UpdateUIPlugin.isPatch(feature, candidate)) {

+					// Unconfigure patch as well.

+					if (site.unconfigure(candidate)==false)

+						totalResult=false;

+				}

+			}

+			catch (CoreException e) {

+				// Tolerate this

+			}

+		}

+		return totalResult;

+	}

+	

 	private void configure(IFeature feature) throws CoreException {

 		IConfiguredSite site = findConfigSite(feature, config);

 		if (site != null) {

@@ -298,13 +353,14 @@
 		for (int i = 0; i < optionalElements.length; i++) {

 			FeatureHierarchyElement fe =

 				(FeatureHierarchyElement) optionalElements[i];

-			Object [] children = fe.getChildren(true);

+			Object[] children = fe.getChildren(true);

 			preserveOptionalState(config, targetSite, children);

 			if (!fe.isEnabled(config)) {

 				IFeature newFeature = fe.getFeature();

 				try {

-					IFeature localFeature = findLocalFeature(targetSite, newFeature);

-					if (localFeature!=null)

+					IFeature localFeature =

+						findLocalFeature(targetSite, newFeature);

+					if (localFeature != null)

 						targetSite.unconfigure(localFeature);

 				} catch (CoreException e) {

 					// Eat this - we will leave with it

@@ -312,9 +368,37 @@
 			}

 		}

 	}

-	private static IFeature findLocalFeature(IConfiguredSite csite, IFeature feature) throws CoreException {

-		IFeatureReference [] refs = csite.getConfiguredFeatures();

-		for (int i=0; i<refs.length; i++) {

+

+	static void collectOldFeatures(

+		IFeature feature,

+		IConfiguredSite targetSite,

+		ArrayList result)

+		throws CoreException {

+		IFeatureReference[] included = feature.getIncludedFeatureReferences();

+		for (int i = 0; i < included.length; i++) {

+			IFeatureReference iref = included[i];

+			IFeature ifeature = iref.getFeature();

+			// find other features and unconfigure

+			String id = iref.getVersionedIdentifier().getIdentifier();

+			IFeature[] sameIds =

+				UpdateUIPlugin.searchSite(id, targetSite, true);

+			for (int j = 0; j < sameIds.length; j++) {

+				IFeature sameId = sameIds[j];

+				// Ignore self.

+				if (sameId.equals(ifeature))

+					continue;

+				result.add(sameId);

+			}

+			collectOldFeatures(ifeature, targetSite, result);

+		}

+	}

+

+	private static IFeature findLocalFeature(

+		IConfiguredSite csite,

+		IFeature feature)

+		throws CoreException {

+		IFeatureReference[] refs = csite.getConfiguredFeatures();

+		for (int i = 0; i < refs.length; i++) {

 			IFeatureReference ref = refs[i];

 			VersionedIdentifier refVid = ref.getVersionedIdentifier();

 			if (feature.getVersionedIdentifier().equals(refVid))

diff --git a/update/org.eclipse.update.ui/src/org/eclipse/update/internal/ui/wizards/NewUpdatesWizard.java b/update/org.eclipse.update.ui/src/org/eclipse/update/internal/ui/wizards/NewUpdatesWizard.java
index 08024a7..b7deac0 100644
--- a/update/org.eclipse.update.ui/src/org/eclipse/update/internal/ui/wizards/NewUpdatesWizard.java
+++ b/update/org.eclipse.update.ui/src/org/eclipse/update/internal/ui/wizards/NewUpdatesWizard.java
@@ -265,7 +265,9 @@
 	private boolean unconfigure(IFeature feature) throws CoreException {
 		IConfiguredSite site = findConfigSite(feature, config);
 		if (site != null) {
-			return site.unconfigure(feature);
+			boolean result = site.unconfigure(feature);
+			if (!result) return false;
+			return InstallWizard.unconfigurePatches(site, feature);
 		}
 		return false;
 	}