/******************************************************************************* * Copyright (c) 2014 Mentor Graphics 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: * Mentor Graphics - initial API and implementation *******************************************************************************/ package com.codesourcery.internal.installer; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.equinox.internal.p2.director.QueryableArray; import org.eclipse.equinox.internal.p2.director.SimplePlanner; import org.eclipse.equinox.internal.p2.engine.InstallableUnitOperand; import org.eclipse.equinox.internal.p2.engine.Operand; import org.eclipse.equinox.internal.p2.engine.ProvisioningPlan; import org.eclipse.equinox.p2.core.IProvisioningAgent; import org.eclipse.equinox.p2.engine.IProvisioningPlan; import org.eclipse.equinox.p2.engine.ProvisioningContext; import org.eclipse.equinox.p2.metadata.IInstallableUnit; import org.eclipse.equinox.p2.metadata.IRequirement; import org.eclipse.equinox.p2.planner.IProfileChangeRequest; import org.eclipse.equinox.p2.query.IQueryResult; import org.eclipse.equinox.p2.query.IQueryable; import org.eclipse.equinox.p2.query.QueryUtil; /** * Extension of org.eclipse.equinox.internal.p2.director.SimplePlanner that orders * plan operands to install and update IUs according dependencies between them. * Does not perform removal ordering. * * See reason for this planner here: * http://dev.eclipse.org/mhonarc/lists/p2-dev/msg05399.html */ @SuppressWarnings("restriction") public class OrderedPlanner extends SimplePlanner { /** * Constructor * * @param agent Provisioning agent */ public OrderedPlanner(IProvisioningAgent agent) { super(agent); } @Override public IProvisioningPlan getProvisioningPlan(IProfileChangeRequest request, ProvisioningContext context, IProgressMonitor monitor) { IProvisioningPlan plan = super.getProvisioningPlan(request, context, monitor); if (plan.getStatus().isOK()) { return orderPlan(plan); } return plan; } /** * Order operands in correct IProvisioningPlan to install IUs according dependencies between them. * @param plan right (status=ok) provisioning plan created by solver * @return new IProvisioningPlan that is the same as original plan, but operands are ordered according to dependecnies between IUs */ public IProvisioningPlan orderPlan(IProvisioningPlan plan) { Operand[] ops = ((ProvisioningPlan) plan).getOperands(); ArrayList<Operand> newOps = new ArrayList<Operand>(ops.length); ArrayList<InstallableUnitOperand> operandsToSort = new ArrayList<InstallableUnitOperand>(ops.length); ArrayList<Operand> misc = new ArrayList<Operand>(ops.length); // First of all we are interested only in additions, not removals, so sort out removals to keep them unchanged for (Operand op : ops) { if (!(op instanceof InstallableUnitOperand)) { // Unexpected, but still misc.add(op); continue; } InstallableUnitOperand iuo = ((InstallableUnitOperand) op); if (iuo.second() == null) { // This is removal, put it unchanged newOps.add(iuo); } else if (iuo.first() == null) { // This is addition operandsToSort.add(iuo); } else { // This is update, sort it too operandsToSort.add(iuo); } } // Create operands -> IUs mappings Map<IInstallableUnit, InstallableUnitOperand> iuToOperand = new HashMap<IInstallableUnit, InstallableUnitOperand>(); for (InstallableUnitOperand o : operandsToSort) { iuToOperand.put(o.second(), o); } // Build dependencies, we are only interested in dependencies between additions IInstallableUnit[] iusToSort = iuToOperand.keySet().toArray(new IInstallableUnit[0]); IQueryable<IInstallableUnit> allIUs = new QueryableArray(iusToSort); // Build index IU ID -> IU for additions Map<String, IInstallableUnit> idToIU = new HashMap<String, IInstallableUnit>(); for (IInstallableUnit iu : iuToOperand.keySet()) { idToIU.put(iu.getId(), iu); } // Build graph representation in and out edges per IU, // edge from B to A means that B is required to install A Map<IInstallableUnit, ArrayList<IInstallableUnit>> outNodesPerIU = new HashMap<IInstallableUnit, ArrayList<IInstallableUnit>>(); Map<IInstallableUnit, ArrayList<IInstallableUnit>> inNodesPerIU = new HashMap<IInstallableUnit, ArrayList<IInstallableUnit>>(); for (IInstallableUnit iu : iuToOperand.keySet()) { ArrayList<IInstallableUnit> inNodes = new ArrayList<IInstallableUnit>(); inNodesPerIU.put(iu, inNodes); ArrayList<IInstallableUnit> outNodes = new ArrayList<IInstallableUnit>(); outNodesPerIU.put(iu, outNodes); Collection<IRequirement> req = iu.getRequirements(); for (IRequirement r : req) { // This is workaround, because simple allIUs.query(QueryUtil.createMatchQuery(r.getMatches()), null) // doesn't work properly. See comment right below. IQueryResult<IInstallableUnit> matches = allIUs.query(QueryUtil.createMatchQuery(r.getMatches()), null); // The situation where there are more that one candidates is an error, ignore it, // because for IUs which are bundles (and therefore contains p2 metadata fragment), // there is dependency from org.eclipse.equinox.p2.eclipse.type // for which query above (QueryUtil.createMatchQuery(r.getMatches())), returns tons of candidates. int matchesSize = 0; for (Iterator<IInstallableUnit> iterator = matches.iterator(); iterator.hasNext(); matchesSize++, iterator.next()); if (matchesSize == 1) { IInstallableUnit requires = matches.iterator().next(); // Do not allow self-references to avoid confusing topolgical sort if (!requires.equals(iu)) { inNodes.add(requires); } } } } // Fill in out nodes, they are used in topological sort: for (IInstallableUnit to : inNodesPerIU.keySet()) { ArrayList<IInstallableUnit> inNodes = inNodesPerIU.get(to); for (IInstallableUnit from : inNodes) { ArrayList<IInstallableUnit> outs = outNodesPerIU.get(from); outs.add(to); } } // Implement topological sort of graph represented by inNodesPerIU and outNodesPerIU ArrayList<IInstallableUnit> sortedIUs = new ArrayList<IInstallableUnit>(); // s <- Set of all nodes with no incoming edges HashSet<IInstallableUnit> s = new HashSet<IInstallableUnit>(); for (IInstallableUnit iu : inNodesPerIU.keySet()) { ArrayList<IInstallableUnit> ins = inNodesPerIU.get(iu); if (ins.size() == 0) { s.add(iu); } } //while s is non-empty do while (!s.isEmpty()) { //remove a node n from S IInstallableUnit n = s.iterator().next(); s.remove(n); //insert n into sortedIUs sortedIUs.add(n); ArrayList<IInstallableUnit> outNodes = outNodesPerIU.get(n); //for each node m with an edge from n to m do for (IInstallableUnit m : outNodes) { //remove edge from the graph ArrayList<IInstallableUnit> mInNodes = inNodesPerIU.get(m); mInNodes.remove(n); //if m has no other incoming edges then insert m into S if (mInNodes.isEmpty()) { s.add(m); } } } // Consistency check if all edges are removed, // should never find a cycle since the plan status is OK boolean cycle = false; for (IInstallableUnit iu : inNodesPerIU.keySet()) { ArrayList<IInstallableUnit> inNodes = inNodesPerIU.get(iu); if (!inNodes.isEmpty()) { cycle = true; break; } } // Merge them back to the end of the list and create new plan ArrayList<InstallableUnitOperand> sortedOperands = new ArrayList<InstallableUnitOperand>(); for (IInstallableUnit iu : sortedIUs) { sortedOperands.add(iuToOperand.get(iu)); } newOps.addAll(sortedOperands); newOps.addAll(misc); // If there are cycles, just add any remaining ops to the end of list if (cycle) { for (Operand op : ops) { if (!newOps.contains(op)) { newOps.add(op); } } } // Return new sorted plan ProvisioningPlan np = new ProvisioningPlan(plan.getStatus(), plan.getProfile(), newOps.toArray(new Operand[newOps.size()]), plan.getContext(), plan.getInstallerPlan()); return np; } }