Finalized support for user defined optimization function.
diff --git a/org.eclipse.equinox.p2.cudf/src/main/java/org/eclipse/equinox/p2/cudf/Main.java b/org.eclipse.equinox.p2.cudf/src/main/java/org/eclipse/equinox/p2/cudf/Main.java
index 5924964..6080cff 100644
--- a/org.eclipse.equinox.p2.cudf/src/main/java/org/eclipse/equinox/p2/cudf/Main.java
+++ b/org.eclipse.equinox.p2.cudf/src/main/java/org/eclipse/equinox/p2/cudf/Main.java
@@ -146,17 +146,17 @@
 		return result;
 	}
 
-	private static boolean validateOptions(Options options) {
+	private static boolean validateOptions(Options theOptions) {
 		boolean error = false;
 		//		if (!"paranoid".equalsIgnoreCase(options.objective) && !"trendy".equalsIgnoreCase(options.objective) && !"p2".equalsIgnoreCase(options.objective)) {
 		//			printFail("Wrong Optimization criteria: " + options.objective);
 		//			error = true;
 		//		}
-		if (options.input == null || !options.input.exists()) {
+		if (theOptions.input == null || !theOptions.input.exists()) {
 			printFail("Missing input file.");
 			error = true;
 		}
-		if (options.timeout != null && !options.timeout.equals("default") && !options.timeout.endsWith("c") && !options.timeout.endsWith("s")) {
+		if (theOptions.timeout != null && !theOptions.timeout.equals("default") && !theOptions.timeout.endsWith("c") && !theOptions.timeout.endsWith("s")) {
 			printFail("Timeout should be either <number>s (100s) or <number>c (100c)");
 			error = true;
 		}
@@ -192,14 +192,14 @@
 		System.exit(0);
 	}
 
-	private static void logOptions(Options options) {
-		if (!options.verbose)
+	private static void logOptions(Options theOptions) {
+		if (!theOptions.verbose)
 			return;
 		Log.println("Solver launched on " + new Date());
-		Log.println("Using input file " + options.input.getAbsolutePath());
-		Log.println("Using ouput file " + (options.output == null ? "STDOUT" : options.output.getAbsolutePath()));
-		Log.println("Objective function " + options.objective);
-		Log.println("Timeout " + options.timeout);
+		Log.println("Using input file " + theOptions.input.getAbsolutePath());
+		Log.println("Using ouput file " + (theOptions.output == null ? "STDOUT" : theOptions.output.getAbsolutePath()));
+		Log.println("Objective function " + theOptions.objective);
+		Log.println("Timeout " + theOptions.timeout);
 	}
 
 	private static void logVmDetails() {
@@ -231,15 +231,15 @@
 
 	private static ProfileChangeRequest parseCUDF(File file) {
 		Log.println("Parsing ...");
-		long begin = System.currentTimeMillis();
-		ProfileChangeRequest result = new Parser().parse(file, options.objective.equals(Options.TRENDY) || options.objective.contains("recommended"));
-		long end = System.currentTimeMillis();
-		Log.println(("Parsing done (" + (end - begin) / 1000.0 + "s)."));
+		long myBegin = System.currentTimeMillis();
+		ProfileChangeRequest result = new Parser().parse(file, options.objective.equals(Options.TRENDY) || options.objective.contains("recommend"));
+		long myEnd = System.currentTimeMillis();
+		Log.println(("Parsing done (" + (myEnd - myBegin) / 1000.0 + "s)."));
 		return result;
 	}
 
-	private static void printSolution(Collection state, Options options) {
-		if (options.sort) {
+	private static void printSolution(Collection state, Options theOptions) {
+		if (theOptions.sort) {
 			ArrayList tmp = new ArrayList(state);
 			Collections.sort(tmp);
 			state = tmp;
diff --git a/org.eclipse.equinox.p2.cudf/src/main/java/org/eclipse/equinox/p2/cudf/Parser.java b/org.eclipse.equinox.p2.cudf/src/main/java/org/eclipse/equinox/p2/cudf/Parser.java
index fc9b6ee..d3aec70 100644
--- a/org.eclipse.equinox.p2.cudf/src/main/java/org/eclipse/equinox/p2/cudf/Parser.java
+++ b/org.eclipse.equinox.p2.cudf/src/main/java/org/eclipse/equinox/p2/cudf/Parser.java
@@ -125,6 +125,8 @@
 					handleRecommends(line);
 				} else if (line.startsWith("keep: ")) {
 					handleKeep(line);
+				// } else {
+				//	Log.println("Ignoring line:" + line);
 				}
 			}
 		} catch (FileNotFoundException e) {
diff --git a/org.eclipse.equinox.p2.cudf/src/main/java/org/eclipse/equinox/p2/cudf/solver/OptimizationFunction.java b/org.eclipse.equinox.p2.cudf/src/main/java/org/eclipse/equinox/p2/cudf/solver/OptimizationFunction.java
index 54626eb..03a66d4 100644
--- a/org.eclipse.equinox.p2.cudf/src/main/java/org/eclipse/equinox/p2/cudf/solver/OptimizationFunction.java
+++ b/org.eclipse.equinox.p2.cudf/src/main/java/org/eclipse/equinox/p2/cudf/solver/OptimizationFunction.java
@@ -25,7 +25,9 @@
 	protected List changeVariables = new ArrayList();
 	protected List nouptodateVariables = new ArrayList();
 	protected List newVariables = new ArrayList();
+	protected List unmetVariables = new ArrayList();
 	protected List optionalityVariables;
+	protected List optionalityPairs;
 
 	public abstract List createOptimizationFunction(InstallableUnit metaIu);
 
@@ -144,6 +146,26 @@
 		}
 	}
 
+	protected void unmetRecommends(List weightedObjects, BigInteger weight, InstallableUnit metaIu) {
+		for (Iterator iterator = optionalityPairs.iterator(); iterator.hasNext();) {
+			Pair entry = (Pair) iterator.next();
+			if (entry.left == metaIu) {
+				weightedObjects.add(WeightedObject.newWO(entry.right, weight));
+				continue;
+			}
+
+			Projector.AbstractVariable abs = new Projector.AbstractVariable(entry.left.toString() + entry.right);
+			try {
+				dependencyHelper.and("OPTX", abs, new Object[] {entry.right, entry.left});
+			} catch (ContradictionException e) {
+				// should never happen
+				e.printStackTrace();
+			}
+			weightedObjects.add(WeightedObject.newWO(abs, weight));
+			unmetVariables.add(abs);
+		}
+	}
+
 	protected void niou(List weightedObjects, BigInteger weight, InstallableUnit metaIu) {
 		Set s = slice.entrySet();
 		for (Iterator iterator = s.iterator(); iterator.hasNext();) {
@@ -179,8 +201,12 @@
 	}
 
 	protected void optional(List weightedObjects, BigInteger weight, InstallableUnit metaIu) {
-		for (Iterator it = optionalityVariables.iterator(); it.hasNext();) {
-			weightedObjects.add(WeightedObject.newWO(it.next(), weight));
+		for (Iterator it = optionalityPairs.iterator(); it.hasNext();) {
+			Pair pair = (Pair) it.next();
+			weightedObjects.add(WeightedObject.newWO(pair.right, weight));
+			if (pair.left != metaIu) {
+				unmetVariables.add(pair.right);
+			}
 		}
 	}
 
diff --git a/org.eclipse.equinox.p2.cudf/src/main/java/org/eclipse/equinox/p2/cudf/solver/Pair.java b/org.eclipse.equinox.p2.cudf/src/main/java/org/eclipse/equinox/p2/cudf/solver/Pair.java
index 9b25623..dbd658d 100644
--- a/org.eclipse.equinox.p2.cudf/src/main/java/org/eclipse/equinox/p2/cudf/solver/Pair.java
+++ b/org.eclipse.equinox.p2.cudf/src/main/java/org/eclipse/equinox/p2/cudf/solver/Pair.java
@@ -15,4 +15,8 @@
 		this.left = left;
 		this.right = right;
 	}
+
+	public String toString() {
+		return "(" + left + "," + right + ")";
+	}
 }
\ No newline at end of file
diff --git a/org.eclipse.equinox.p2.cudf/src/main/java/org/eclipse/equinox/p2/cudf/solver/Projector.java b/org.eclipse.equinox.p2.cudf/src/main/java/org/eclipse/equinox/p2/cudf/solver/Projector.java
index dcca800..919de51 100644
--- a/org.eclipse.equinox.p2.cudf/src/main/java/org/eclipse/equinox/p2/cudf/solver/Projector.java
+++ b/org.eclipse.equinox.p2.cudf/src/main/java/org/eclipse/equinox/p2/cudf/solver/Projector.java
@@ -54,6 +54,7 @@
 	private OptimizationFunction optFunction;
 
 	private List optionalityVariables;
+	private List optionalityPairs;
 
 	static class AbstractVariable {
 		private String str;
@@ -79,6 +80,7 @@
 		result = new MultiStatus(Main.PLUGIN_ID, IStatus.OK, Messages.Planner_Problems_resolving_plan, null);
 		assumptions = new ArrayList();
 		optionalityVariables = new ArrayList();
+		optionalityPairs = new ArrayList();
 	}
 
 	private void purgeIU(InstallableUnit iu) {
@@ -159,15 +161,15 @@
 		}
 	}
 
-	private OptimizationFunction getOptimizationFactory(String optFunction) {
+	private OptimizationFunction getOptimizationFactory(String optFunctionName) {
 		OptimizationFunction function = null;
-		if ("paranoid".equalsIgnoreCase(optFunction)) {
+		if ("paranoid".equalsIgnoreCase(optFunctionName)) {
 			function = new ParanoidOptimizationFunction(); //paranoid
-		} else if ("trendy".equalsIgnoreCase(optFunction)) {
+		} else if ("trendy".equalsIgnoreCase(optFunctionName)) {
 			function = new TrendyOptimizationFunction(); // trendy
 		} else {
 			// throw new IllegalArgumentException("Unknown optimisation function: " + optFunction);
-			function = new UserDefinedOptimizationFunction(optFunction);
+			function = new UserDefinedOptimizationFunction(optFunctionName);
 		}
 		Log.println(" Optimization function: " + function.getName());
 		function.slice = slice;
@@ -175,6 +177,7 @@
 		function.picker = picker;
 		function.dependencyHelper = dependencyHelper;
 		function.optionalityVariables = optionalityVariables;
+		function.optionalityPairs = optionalityPairs;
 		return function;
 	}
 
@@ -253,6 +256,7 @@
 				matches.add(abs);
 				createImplication(iu, matches, Explanation.OPTIONAL_REQUIREMENT);
 				optionalityVariables.add(abs);
+				optionalityPairs.add(new Pair(iu, abs));
 			}
 		}
 	}
@@ -312,13 +316,6 @@
 		dependencyHelper.implication(new Object[] {left}).implies(right.toArray()).named(name);
 	}
 
-	private void createImplication(Object[] left, List right, Explanation name) throws ContradictionException {
-		if (DEBUG) {
-			Tracing.debug(name + ": " + Arrays.asList(left) + "->" + right); //$NON-NLS-1$ //$NON-NLS-2$
-		}
-		dependencyHelper.implication(left).implies(right.toArray()).named(name);
-	}
-
 	//Create constraints to deal with singleton
 	//When there is a mix of singleton and non singleton, several constraints are generated
 	private void createConstraintsForSingleton() throws ContradictionException {
@@ -357,39 +354,6 @@
 		}
 	}
 
-	private void createIncompatibleValues(AbstractVariable v1, AbstractVariable v2) throws ContradictionException {
-		AbstractVariable[] vars = {v1, v2};
-		if (DEBUG) {
-			StringBuffer b = new StringBuffer();
-			for (int i = 0; i < vars.length; i++) {
-				b.append(vars[i].toString());
-			}
-			Tracing.debug("At most 1 of " + b); //$NON-NLS-1$
-		}
-		dependencyHelper.atMost(1, vars).named(Explanation.OPTIONAL_REQUIREMENT);
-	}
-
-	//	private void createOptionalityExpression(InstallableUnit iu, List optionalRequirements) throws ContradictionException {
-	//		if (optionalRequirements.isEmpty())
-	//			return;
-	//		AbstractVariable noop = getNoOperationVariable(iu);
-	//		for (Iterator i = optionalRequirements.iterator(); i.hasNext();) {
-	//			AbstractVariable abs = (AbstractVariable) i.next();
-	//			createIncompatibleValues(abs, noop);
-	//		}
-	//		optionalRequirements.add(noop);
-	//		createImplication(iu, optionalRequirements, Explanation.OPTIONAL_REQUIREMENT);
-	//	}
-
-	private AbstractVariable getNoOperationVariable(InstallableUnit iu) {
-		AbstractVariable v = (AbstractVariable) noopVariables.get(iu);
-		if (v == null) {
-			v = new AbstractVariable();
-			noopVariables.put(iu, v);
-		}
-		return v;
-	}
-
 	private void createAtMostOne(InstallableUnit[] ius) throws ContradictionException {
 		if (DEBUG) {
 			StringBuffer b = new StringBuffer();
diff --git a/org.eclipse.equinox.p2.cudf/src/main/java/org/eclipse/equinox/p2/cudf/solver/TrendyOptimizationFunction.java b/org.eclipse.equinox.p2.cudf/src/main/java/org/eclipse/equinox/p2/cudf/solver/TrendyOptimizationFunction.java
index 9e77ced..3fc17a1 100644
--- a/org.eclipse.equinox.p2.cudf/src/main/java/org/eclipse/equinox/p2/cudf/solver/TrendyOptimizationFunction.java
+++ b/org.eclipse.equinox.p2.cudf/src/main/java/org/eclipse/equinox/p2/cudf/solver/TrendyOptimizationFunction.java
@@ -29,12 +29,8 @@
 
 public class TrendyOptimizationFunction extends OptimizationFunction {
 
-	private InstallableUnit metaIu;
-
 	public List createOptimizationFunction(InstallableUnit metaIu) {
-		this.metaIu = metaIu;
 		List weightedObjects = new ArrayList();
-		Collection ius = slice.values();
 		BigInteger weight = BigInteger.valueOf(slice.size() + 1);
 		removed(weightedObjects, weight.multiply(weight).multiply(weight), metaIu);
 		notuptodate(weightedObjects, weight.multiply(weight), metaIu);
@@ -72,7 +68,7 @@
 		}
 		System.out.println("# Not up-to-date packages: " + proof);
 		proof.clear();
-		for (Iterator it = optionalityVariables.iterator(); it.hasNext();) {
+		for (Iterator it = unmetVariables.iterator(); it.hasNext();) {
 			Object var = it.next();
 			if (dependencyHelper.getBooleanValueFor(var)) {
 				recommends++;
diff --git a/org.eclipse.equinox.p2.cudf/src/main/java/org/eclipse/equinox/p2/cudf/solver/TwoTierMap.java b/org.eclipse.equinox.p2.cudf/src/main/java/org/eclipse/equinox/p2/cudf/solver/TwoTierMap.java
index fed53d9..6c8db20 100644
--- a/org.eclipse.equinox.p2.cudf/src/main/java/org/eclipse/equinox/p2/cudf/solver/TwoTierMap.java
+++ b/org.eclipse.equinox.p2.cudf/src/main/java/org/eclipse/equinox/p2/cudf/solver/TwoTierMap.java
@@ -232,8 +232,8 @@
 		if (key instanceof Object[]) {
 			Object[] keys = (Object[]) key;
 			return get(keys[0], keys[1]);
-		} else
-			return getAll(key);
+		}
+		return getAll(key);
 	}
 
 	/* (non-Javadoc)
@@ -268,8 +268,8 @@
 		if (key instanceof Object[]) {
 			Object[] keys = (Object[]) key;
 			return remove(keys[0], keys[1]);
-		} else
-			return removeAll(key);
+		}
+		return removeAll(key);
 	}
 
 	public String toString() {
diff --git a/org.eclipse.equinox.p2.cudf/src/main/java/org/eclipse/equinox/p2/cudf/solver/UserDefinedOptimizationFunction.java b/org.eclipse.equinox.p2.cudf/src/main/java/org/eclipse/equinox/p2/cudf/solver/UserDefinedOptimizationFunction.java
index e8fa490..7c150c6 100644
--- a/org.eclipse.equinox.p2.cudf/src/main/java/org/eclipse/equinox/p2/cudf/solver/UserDefinedOptimizationFunction.java
+++ b/org.eclipse.equinox.p2.cudf/src/main/java/org/eclipse/equinox/p2/cudf/solver/UserDefinedOptimizationFunction.java
@@ -14,7 +14,6 @@
 
 	public List createOptimizationFunction(InstallableUnit metaIu) {
 		List weightedObjects = new ArrayList();
-		Collection ius = slice.values();
 		BigInteger weight = BigInteger.valueOf(slice.size() + 1);
 		String[] criteria = optfunction.split(",");
 		BigInteger currentWeight = weight.pow(criteria.length - 1);
@@ -39,6 +38,11 @@
 				currentWeight = currentWeight.divide(weight);
 				continue;
 			}
+			if (criteria[i].endsWith("unmet_recommends")) {
+				optional(weightedObjects, criteria[i].startsWith("+") ? currentWeight.negate() : currentWeight, metaIu);
+				currentWeight = currentWeight.divide(weight);
+				continue;
+			}
 			if (criteria[i].endsWith("changed")) {
 				changed(weightedObjects, criteria[i].startsWith("+") ? currentWeight.negate() : currentWeight, metaIu);
 				currentWeight = currentWeight.divide(weight);
@@ -100,10 +104,10 @@
 				System.out.println("# Not up-to-date packages: " + proof);
 				continue;
 			}
-			if (criteria[i].endsWith("recommended")) {
+			if (criteria[i].endsWith("recommended") || criteria[i].endsWith("unmet_recommends")) {
 				proof.clear();
 				counter = 0;
-				for (Iterator it = optionalityVariables.iterator(); it.hasNext();) {
+				for (Iterator it = unmetVariables.iterator(); it.hasNext();) {
 					Object var = it.next();
 					if (dependencyHelper.getBooleanValueFor(var)) {
 						counter++;