blob: 5c4b392f63c42ea53196c8983f4fcd39c94a1f6d [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2007, 2008 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.internal.p2.director;
import java.io.*;
import java.util.*;
import java.util.Map.Entry;
import org.eclipse.core.runtime.*;
import org.eclipse.equinox.internal.provisional.p2.metadata.IInstallableUnit;
import org.eclipse.equinox.internal.provisional.p2.metadata.RequiredCapability;
import org.eclipse.equinox.internal.provisional.p2.metadata.query.CapabilityQuery;
import org.eclipse.equinox.internal.provisional.p2.metadata.query.InstallableUnitQuery;
import org.eclipse.equinox.internal.provisional.p2.query.Collector;
import org.eclipse.equinox.internal.provisional.p2.query.IQueryable;
import org.eclipse.osgi.util.NLS;
import org.osgi.framework.InvalidSyntaxException;
import org.sat4j.pb.IPBSolver;
import org.sat4j.pb.SolverFactory;
import org.sat4j.pb.reader.OPBEclipseReader2007;
import org.sat4j.reader.ParseFormatException;
import org.sat4j.specs.*;
/**
* This class is the interface between SAT4J and the planner. It produces a
* boolean satisfiability problem, invokes the solver, and converts the solver result
* back into information understandable by the planner.
*/
public class Projector {
private static boolean DEBUG = false;
private IQueryable picker;
private Map variables; //key IU, value corresponding variable in the problem
private Map noopVariables; //key IU, value corresponding no optionality variable in the problem,
private List abstractVariables;
private TwoTierMap slice; //The IUs that have been considered to be part of the problem
private Dictionary selectionContext;
private int varCount = 1;
private ArrayList constraints;
private ArrayList dependencies;
private ArrayList tautologies;
private StringBuffer objective;
private StringBuffer explanation = new StringBuffer("explain: "); //$NON-NLS-1$
private Collection solution;
private File problemFile;
private MultiStatus result;
public Projector(IQueryable q, Dictionary context) {
picker = q;
variables = new HashMap();
noopVariables = new HashMap();
slice = new TwoTierMap();
constraints = new ArrayList();
tautologies = new ArrayList();
dependencies = new ArrayList();
selectionContext = context;
abstractVariables = new ArrayList();
result = new MultiStatus(DirectorActivator.PI_DIRECTOR, IStatus.OK, Messages.Planner_Problems_resolving_plan, null);
}
public void encode(IInstallableUnit[] ius, IProgressMonitor monitor) {
try {
long start = 0;
if (DEBUG) {
start = System.currentTimeMillis();
System.out.println("Start projection: " + start); //$NON-NLS-1$
}
Iterator iusToEncode = picker.query(InstallableUnitQuery.ANY, new Collector(), null).iterator();
while (iusToEncode.hasNext()) {
processIU((IInstallableUnit) iusToEncode.next());
}
createConstraintsForSingleton();
for (int i = 0; i < ius.length; i++) {
createMustHaves(ius[i]);
}
createOptimizationFunction();
persist();
if (DEBUG) {
long stop = System.currentTimeMillis();
System.out.println("Projection complete: " + (stop - start)); //$NON-NLS-1$
}
} catch (IllegalStateException e) {
result.add(new Status(IStatus.ERROR, DirectorActivator.PI_DIRECTOR, e.getMessage(), e));
}
}
//Create an optimization function favoring the highest version of each IU
private void createOptimizationFunction() {
objective = new StringBuffer("min:"); //$NON-NLS-1$
Set s = slice.entrySet();
//Add the abstract variables
for (Iterator iterator = abstractVariables.iterator(); iterator.hasNext();) {
objective.append(" -2 ").append((String) iterator.next()); //$NON-NLS-1$
}
for (Iterator iterator = s.iterator(); iterator.hasNext();) {
Map.Entry entry = (Map.Entry) iterator.next();
HashMap conflictingEntries = (HashMap) entry.getValue();
if (conflictingEntries.size() <= 1) {
objective.append(" 1 ").append(getVariable((IInstallableUnit) conflictingEntries.values().iterator().next())); //$NON-NLS-1$
continue;
}
List toSort = new ArrayList(conflictingEntries.values());
Collections.sort(toSort);
double weight = Math.pow(10, toSort.size() - 1);
int count = toSort.size();
for (Iterator iterator2 = toSort.iterator(); iterator2.hasNext();) {
count--;
objective.append(' ').append((int) weight).append(' ').append(getVariable((IInstallableUnit) iterator2.next()));
weight = weight / 10;
}
}
objective.append(" ;"); //$NON-NLS-1$
}
private void createMustHaves(IInstallableUnit iu) {
tautologies.add(" +1 " + getVariable(iu) + " = 1;"); //$NON-NLS-1$ //$NON-NLS-2$
}
private void createNegation(IInstallableUnit iu) {
tautologies.add(" +1" + getVariable(iu) + " = 0;"); //$NON-NLS-1$//$NON-NLS-2$
}
// Check whether the requirement is applicable
private boolean isApplicable(RequiredCapability req) {
String filter = req.getFilter();
if (filter == null)
return true;
try {
return DirectorActivator.context.createFilter(filter).match(selectionContext);
} catch (InvalidSyntaxException e) {
return false;
}
}
//Write the problem generated into a temporary file
private void persist() {
try {
problemFile = File.createTempFile("p2Encoding", ".opb"); //$NON-NLS-1$//$NON-NLS-2$
BufferedWriter w = new BufferedWriter(new FileWriter(problemFile));
int clauseCount = tautologies.size() + dependencies.size() + constraints.size();
w.write("* #variable= " + varCount + " #constraint= " + clauseCount + " "); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
w.newLine();
w.write("*"); //$NON-NLS-1$
w.newLine();
if (clauseCount == 0) {
w.close();
return;
}
w.write(objective.toString());
w.newLine();
w.newLine();
w.write(explanation + " ;"); //$NON-NLS-1$
w.newLine();
w.newLine();
for (Iterator iterator = dependencies.iterator(); iterator.hasNext();) {
w.write((String) iterator.next());
w.newLine();
}
for (Iterator iterator = constraints.iterator(); iterator.hasNext();) {
w.write((String) iterator.next());
w.newLine();
}
for (Iterator iterator = tautologies.iterator(); iterator.hasNext();) {
w.write((String) iterator.next());
w.newLine();
}
w.close();
} catch (IOException e) {
result.add(new Status(IStatus.ERROR, DirectorActivator.PI_DIRECTOR, NLS.bind(Messages.Planner_Error_saving_opbfile, problemFile), e));
}
}
private boolean isApplicable(IInstallableUnit iu) {
String enablementFilter = iu.getFilter();
if (enablementFilter == null)
return true;
try {
return DirectorActivator.context.createFilter(enablementFilter).match(selectionContext);
} catch (InvalidSyntaxException e) {
return false;
}
}
public void processIU(IInstallableUnit iu) {
slice.put(iu.getId(), iu.getVersion(), iu);
explanation.append(" ").append(getVariable(iu)); //$NON-NLS-1$
if (!isApplicable(iu)) {
createNegation(iu);
return;
}
RequiredCapability[] reqs = iu.getRequiredCapabilities();
if (reqs.length == 0) {
return;
}
for (int i = 0; i < reqs.length; i++) {
if (!isApplicable(reqs[i]))
continue;
expandRequirement(iu, reqs[i]);
}
addOptionalityExpression();
}
private void addOptionalityExpression() {
if (optionalityExpression != null && countOptionalIUs > 0)
dependencies.add(optionalityExpression + " >= 0;"); //$NON-NLS-1$
optionalityExpression = null;
countOptionalIUs = 0;
}
private String optionalityExpression = null;
private int countOptionalIUs = 0;
private void expandOptionalRequirement(IInstallableUnit iu, RequiredCapability req) {
String abstractVar = getAbstractVariable();
String expression = " -1 " + abstractVar; //$NON-NLS-1$
Collector matches = picker.query(new CapabilityQuery(req), new Collector(), null);
if (optionalityExpression == null)
optionalityExpression = " -1 " + getVariable(iu) + " 1 " + getNoOptionalVariable(iu); //$NON-NLS-1$ //$NON-NLS-2$
int countMatches = 0;
for (Iterator iterator = matches.iterator(); iterator.hasNext();) {
IInstallableUnit match = (IInstallableUnit) iterator.next();
if (!isApplicable(match))
continue;
countMatches++;
expression += " 1 " + getVariable(match); //$NON-NLS-1$
}
countOptionalIUs += countMatches;
if (countMatches > 0) {
dependencies.add(" -1 " + getNoOptionalVariable(iu) + " -1 " + abstractVar + " >= -1;"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
dependencies.add(expression + " >= 0;"); //$NON-NLS-1$
optionalityExpression += " 1 " + abstractVar; //$NON-NLS-1$
}
if (DEBUG)
System.out.println("No IU found to satisfy optional dependency of " + iu + " req " + req); //$NON-NLS-1$//$NON-NLS-2$
}
private void expandNormalRequirement(IInstallableUnit iu, RequiredCapability req) {
//Generate the regular requirement
String expression = "-1 " + getVariable(iu); //$NON-NLS-1$
Collector matches = picker.query(new CapabilityQuery(req), new Collector(), null);
int countMatches = 0;
for (Iterator iterator = matches.iterator(); iterator.hasNext();) {
IInstallableUnit match = (IInstallableUnit) iterator.next();
if (!isApplicable(match))
continue;
countMatches++;
expression += " +1 " + getVariable(match); //$NON-NLS-1$
}
if (countMatches > 0) {
dependencies.add(expression + " >= 0;"); //$NON-NLS-1$
} else {
result.add(new Status(IStatus.WARNING, DirectorActivator.PI_DIRECTOR, NLS.bind(Messages.Planner_Unsatisfied_dependency, iu, req)));
createNegation(iu);
}
}
private void expandRequirement(IInstallableUnit iu, RequiredCapability req) {
if (req.isOptional())
expandOptionalRequirement(iu, req);
else
expandNormalRequirement(iu, req);
}
//Create constraints to deal with singleton
//When there is a mix of singleton and non singleton, several constraints are generated
private void createConstraintsForSingleton() {
Set s = slice.entrySet();
for (Iterator iterator = s.iterator(); iterator.hasNext();) {
Map.Entry entry = (Map.Entry) iterator.next();
HashMap conflictingEntries = (HashMap) entry.getValue();
if (conflictingEntries.size() < 2)
continue;
Collection conflictingVersions = conflictingEntries.values();
String singletonRule = ""; //$NON-NLS-1$
ArrayList nonSingleton = new ArrayList();
int countSingleton = 0;
for (Iterator conflictIterator = conflictingVersions.iterator(); conflictIterator.hasNext();) {
IInstallableUnit conflictElt = (IInstallableUnit) conflictIterator.next();
if (conflictElt.isSingleton()) {
singletonRule += " -1 " + getVariable(conflictElt); //$NON-NLS-1$
countSingleton++;
} else {
nonSingleton.add(conflictElt);
}
}
if (countSingleton == 0)
continue;
for (Iterator iterator2 = nonSingleton.iterator(); iterator2.hasNext();) {
constraints.add(singletonRule + " -1 " + getVariable((IInstallableUnit) iterator2.next()) + " >= -1;"); //$NON-NLS-1$ //$NON-NLS-2$
}
singletonRule += " >= -1;"; //$NON-NLS-1$
constraints.add(singletonRule);
}
}
//Return the corresponding variable
private String getVariable(IInstallableUnit iu) {
String v = (String) variables.get(iu);
if (v == null) {
v = new String("x" + varCount++); //$NON-NLS-1$
variables.put(iu, v);
}
return v;
}
private String getAbstractVariable() {
String newVar = new String("x" + varCount++); //$NON-NLS-1$
abstractVariables.add(newVar);
return newVar;
}
private String getNoOptionalVariable(IInstallableUnit iu) {
String v = (String) noopVariables.get(iu);
if (v == null) {
v = new String("x" + varCount++); //$NON-NLS-1$
noopVariables.put(iu, v);
}
return v;
}
public IStatus invokeSolver(IProgressMonitor monitor) {
if (result.getSeverity() == IStatus.ERROR)
return result;
IPBSolver solver = SolverFactory.newEclipseP2();
solver.setTimeout(60);
OPBEclipseReader2007 reader = new OPBEclipseReader2007(solver);
// CNF filename is given on the command line
long start = System.currentTimeMillis();
if (DEBUG)
System.out.println("Invoking solver: " + start); //$NON-NLS-1$
FileReader fr = null;
try {
fr = new FileReader(problemFile);
IProblem problem = reader.parseInstance(fr);
if (problem.isSatisfiable()) {
if (DEBUG) {
System.out.println("Satisfiable !"); //$NON-NLS-1$
System.out.println(reader.decode(problem.model()));
}
backToIU(problem);
long stop = System.currentTimeMillis();
if (DEBUG)
System.out.println("Solver solution found: " + (stop - start)); //$NON-NLS-1$
} else {
if (DEBUG)
System.out.println("Unsatisfiable !"); //$NON-NLS-1$
result.merge(new Status(IStatus.ERROR, DirectorActivator.PI_DIRECTOR, NLS.bind(Messages.Planner_Unsatisfiable_problem, problemFile)));
}
} catch (FileNotFoundException e) {
result.add(new Status(IStatus.ERROR, DirectorActivator.PI_DIRECTOR, NLS.bind(Messages.Planner_Missing_opb_file, problemFile)));
} catch (ParseFormatException e) {
result.add(new Status(IStatus.ERROR, DirectorActivator.PI_DIRECTOR, NLS.bind(Messages.Planner_Format_error, problemFile)));
} catch (ContradictionException e) {
result.merge(new Status(IStatus.ERROR, DirectorActivator.PI_DIRECTOR, NLS.bind(Messages.Planner_Trivial_exception, problemFile)));
} catch (TimeoutException e) {
result.merge(new Status(IStatus.ERROR, DirectorActivator.PI_DIRECTOR, NLS.bind(Messages.Planner_Timeout, problemFile)));
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (fr != null)
fr.close();
} catch (IOException e) {
//ignore
}
problemFile.delete();
}
return result;
}
private void backToIU(IProblem problem) {
solution = new ArrayList();
for (Iterator allIUs = variables.entrySet().iterator(); allIUs.hasNext();) {
Entry entry = (Entry) allIUs.next();
int match = Integer.parseInt(((String) entry.getValue()).substring(1));
if (problem.model(match)) {
solution.add(((IInstallableUnit) entry.getKey()).unresolved());
}
}
}
private void printSolution(Collection state) {
ArrayList l = new ArrayList(state);
Collections.sort(l);
System.out.println("Numbers of IUs selected:" + l.size()); //$NON-NLS-1$
for (Iterator iterator = l.iterator(); iterator.hasNext();) {
System.out.println(iterator.next());
}
}
public Collection extractSolution() {
if (DEBUG)
printSolution(solution);
return solution;
}
}