/******************************************************************************* * Copyright (c) 2015 Red Hat, Inc. * 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: * Red Hat, Inc. - initial API and implementation *******************************************************************************/ package org.eclipse.m2e.core.internal.lifecyclemapping; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.IConfigurationElement; import org.eclipse.osgi.util.NLS; import org.codehaus.plexus.util.dag.CycleDetectedException; import org.codehaus.plexus.util.dag.DAG; import org.codehaus.plexus.util.dag.TopologicalSorter; import org.eclipse.m2e.core.internal.Messages; /** * Sorts a list of Project Configurator Metadata according to their matching {@link IConfigurationElement}s */ public class ProjectConfigurationElementSorter { private List<String> sortedConfigurators; private Map<String, String> incompleteConfigurators; private Set<String> missingRequiredConfigurators; private Set<String> allSecondaryConfigurators = new HashSet<>(); private Map<String, List<String>> primaryConfigurators = new HashMap<>(); /** * Sorts a list of ids, ordering it by Project Configurator {@link IConfigurationElement}s * * @param configuratorIds, a collection of configurator ids to sort * @param configurators, a map of [id:project configurator's {@link IConfigurationElement}] * @throws CycleDetectedException if a cycle is detected between configurators */ public ProjectConfigurationElementSorter(Collection<String> configuratorIds, Map<String, IConfigurationElement> configurators) throws CycleDetectedException { Assert.isNotNull(configurators, "configuratorConfigElements parameter can not be null"); //DAG including required and optional configurators DAG fullDag = new DAG(); //DAG including required configurators only DAG requirementsDag = new DAG(); Map<String, String> _incompletes = new HashMap<>(); Set<String> _missingIds = new HashSet<>(); //Create a vertex for each configurator. for(String key : configuratorIds) { requirementsDag.addVertex(key); fullDag.addVertex(key); IConfigurationElement configurator = configurators.get(key); if(configurator == null) { _missingIds.add(key); continue; } //Add edge for configurators this configurator should run after String[] runsAfter = safeSplit(configurator.getAttribute("runsAfter")); //fallback to legacy secondaryTo attribute if(runsAfter == null) { String secondaryTo = configurator.getAttribute("secondaryTo"); if(secondaryTo != null) { runsAfter = new String[] {secondaryTo}; } } if(runsAfter != null && runsAfter.length > 0) { allSecondaryConfigurators.add(key); for(String id : runsAfter) { id = id.trim(); if(id.isEmpty()) { continue; } boolean isRequired = !id.endsWith("?"); String predecessorId = sanitize(id); if(isRequired) { requirementsDag.addEdge(key, predecessorId); } IConfigurationElement predecessor = configurators.get(predecessorId); if(predecessor == null) { if(isRequired) { _missingIds.add(predecessorId); _incompletes.put(key, NLS.bind(Messages.ProjectConfiguratorToRunAfterNotAvailable, key, predecessorId)); } } else { fullDag.addEdge(key, predecessorId); } } } //Add edges for configurators this configurator should run before String[] runsBefore = safeSplit(configurator.getAttribute("runsBefore")); if(runsBefore != null) { for(String id : runsBefore) { id = id.trim(); if(id.isEmpty()) { continue; } boolean isRequired = id.endsWith("*"); String successorId = sanitize(id); if(isRequired) { requirementsDag.addEdge(successorId, key); } IConfigurationElement successor = configurators.get(successorId); if(successor == null) { if(isRequired) { //missing required matching configElement _missingIds.add(successorId); _incompletes.put(key, NLS.bind(Messages.ProjectConfiguratorToRunBeforeNotAvailable, key, successorId)); } } else { fullDag.addEdge(successorId, key); } } } } //1st sort, leaving out optional configurators, to detect broken required dependencies List<String> sortedExecutions = TopologicalSorter.sort(requirementsDag); for(String id : sortedExecutions) { boolean isIncompleteOrMissing = _missingIds.contains(id) || _incompletes.containsKey(id); if(isIncompleteOrMissing) { Set<String> dependents = new HashSet<>(); getDependents(id, requirementsDag, dependents); for(String next : dependents) { if(configuratorIds.contains(next) && (_missingIds.contains(next) || !_incompletes.containsKey(next))) { _incompletes.put(next, NLS.bind(Messages.ProjectConfiguratorNotAvailable, id, next)); } } } } //2nd sort, including optional dependencies sortedExecutions = TopologicalSorter.sort(fullDag); List<String> _sortedConfigurators = new ArrayList<>(sortedExecutions.size()); for(String id : sortedExecutions) { //Remove incomplete metadata if(!configuratorIds.contains(id) || _incompletes.containsKey(id) || _missingIds.contains(id)) { continue; } List<String> predecessors = fullDag.getChildLabels(id); boolean addAsPrimary = true; if(predecessors != null && !predecessors.isEmpty()) { for(String p : predecessors) { if(configuratorIds.contains(p)) { addAsPrimary = false; break; } } } if(addAsPrimary) { Set<String> secondaries = new LinkedHashSet<>(); getDependents(id, fullDag, secondaries); primaryConfigurators.put(id, new ArrayList<>(secondaries)); allSecondaryConfigurators.addAll(secondaries); } //add to resulting list _sortedConfigurators.add(id); } sortedConfigurators = Collections.unmodifiableList(_sortedConfigurators); incompleteConfigurators = Collections.unmodifiableMap(_incompletes); missingRequiredConfigurators = Collections.unmodifiableSet(_missingIds); } public ProjectConfigurationElementSorter(Map<String, IConfigurationElement> configurators) throws CycleDetectedException { this(configurators.keySet(), configurators); } private static void getDependents(String id, DAG dag, Set<String> dependents) { List<String> parents = dag.getParentLabels(id); if(parents == null || parents.isEmpty()) { return; } for(String parent : parents) { if(dependents.add(parent)) { getDependents(parent, dag, dependents); } } } private static String sanitize(String id) { return (id.endsWith("?") || id.endsWith("*")) ? id.substring(0, id.length() - 1) : id; } private static String[] safeSplit(String value) { return value == null ? null : value.split(","); } /** * @return a sorted, unmodifiable list of configurator ids */ public List<String> getSortedConfigurators() { if(sortedConfigurators == null) { sortedConfigurators = Collections.emptyList(); } return sortedConfigurators; } /** * @return an unmodifiable Map of ids of incomplete configurators; (each value corresponds to the reason why it was * found to be incomplete) */ public Map<String, String> getIncompleteConfigurators() { if(incompleteConfigurators == null) { incompleteConfigurators = Collections.emptyMap(); } return incompleteConfigurators; } /** * @return an unmodifiable set of missing configurator ids. */ public Set<String> getMissingConfigurators() { if(missingRequiredConfigurators == null) { missingRequiredConfigurators = Collections.emptySet(); } return missingRequiredConfigurators; } /** * @return an ordered list of secondary configurator ids to this primaryConfigurator. */ public List<String> getSecondaryConfigurators(String primaryConfigurator) { List<String> secondaries = primaryConfigurators.get(primaryConfigurator); if(secondaries == null) { secondaries = Collections.emptyList(); } return secondaries; } /** * @return true if a configurator id is a root configurator (i.e. has no parent) */ public boolean isRootConfigurator(String configuratorId) { if(configuratorId == null || incompleteConfigurators.containsKey(configuratorId)) { return false; } boolean isPrimary = primaryConfigurators.containsKey(configuratorId); boolean isSecondary = allSecondaryConfigurators.contains(configuratorId); boolean isRoot = (isPrimary && (primaryConfigurators.size() == 1 || !isSecondary)) || (!isPrimary && !isSecondary); return isRoot; } public String toString() { return "ProjectConfigurationElementSorter [" + getSortedConfigurators() + "]"; } }