/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
package org.eclipse.m2e.core.internal.project.conversion;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
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.codehaus.plexus.util.dag.Vertex;
import org.eclipse.m2e.core.internal.IMavenConstants;
import org.eclipse.m2e.core.internal.Messages;
import org.eclipse.m2e.core.project.conversion.AbstractProjectConversionParticipant;
/**
* Sorts a list of {@link AbstractProjectConversionParticipant} (converters).
* <ul>
* <li>Declares all converters as vertices of a Directed Acyclic Graph ({@link DAG})</li>
* <li>Adds edges between each converter and its preceding (see
* {@link AbstractProjectConversionParticipant#getPrecedingConverterIds()}) and succeeding (see
* {@link AbstractProjectConversionParticipant#getSucceedingConverterIds()}) converters</li>
* <li>ignores all unknown converter dependencies</li>
* <li>Does a topological sort on the graph</li>
* <li>The sorted list in unmodifiable.</li>
* </ul>
* <p>
* This implementation was inspired by Apache Maven's {@link org.apache.maven.project.ProjectSorter}
* </p>
*
* @author Fred Bricon
* @throws CycleDetectedException if a cycle is detected between project conversion participant dependencies
* @throws DuplicateConversionParticipantException if any project conversion participant ids are duplicated
* @see DAG
*/
public class ProjectConversionParticipantSorter {
private List<AbstractProjectConversionParticipant> sortedConverters;
@SuppressWarnings("unchecked")
public ProjectConversionParticipantSorter(List<AbstractProjectConversionParticipant> converters)
throws CycleDetectedException, DuplicateConversionParticipantException {
if(converters == null) {
throw new IllegalArgumentException("converters parameter can not be null");
}
if(converters.isEmpty()) {
this.sortedConverters = Collections.emptyList();
return;
}
if(converters.size() == 1) {
this.sortedConverters = Collections.singletonList(converters.get(0));
return;
}
DAG dag = new DAG();
Map<String, AbstractProjectConversionParticipant> converterMap = new HashMap<String, AbstractProjectConversionParticipant>(
converters.size());
//Create a vertex for each converter. Duplicates not allowed!
for(AbstractProjectConversionParticipant converter : converters) {
String converterId = converter.getId();
AbstractProjectConversionParticipant conflictingConverter = converterMap.put(converterId, converter);
if(conflictingConverter != null) {
IStatus error = new Status(IStatus.ERROR, IMavenConstants.PLUGIN_ID, NLS.bind(
Messages.ProjectConversion_error_duplicate_conversion_participant, converterId));
throw new DuplicateConversionParticipantException(error);
}
dag.addVertex(converterId);
}
//Add edges
for(Vertex converterVx : (List<Vertex>) dag.getVerticies()) {
String converterId = converterVx.getLabel();
AbstractProjectConversionParticipant converter = converterMap.get(converterId);
//Add edges for all the converters this converter should run after
String[] predecessors = converter.getPrecedingConverterIds();
if(predecessors != null) {
for(String id : predecessors) {
Vertex predecessor = dag.getVertex(id);
if(predecessor != null) {
dag.addEdge(converterVx, predecessor);
}
}
}
//Add edges for all the converters this converter should run before
String[] successors = converter.getSucceedingConverterIds();
if(successors != null) {
for(String id : successors) {
Vertex successor = dag.getVertex(id);
if(successor != null) {
dag.addEdge(successor, converterVx);
}
}
}
}
List<String> sortedConverterIds = TopologicalSorter.sort(dag);
List<AbstractProjectConversionParticipant> sortedConverters = new ArrayList<AbstractProjectConversionParticipant>(
converters.size());
for(String id : sortedConverterIds) {
sortedConverters.add(converterMap.get(id));
}
this.sortedConverters = Collections.unmodifiableList(sortedConverters);
}
/**
* @return a sorted, unmodifiable list of the {@link AbstractProjectConversionParticipant}.
*/
public List<AbstractProjectConversionParticipant> getSortedConverters() {
return sortedConverters;
}
}