[MNG-7820] Get rid of plexus-utils DAG class (#1250)

This commit is contained in:
Guillaume Nodet 2023-09-22 08:02:04 +02:00 committed by GitHub
parent 0c37cff681
commit e91cee9a2d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 416 additions and 24 deletions

View File

@ -327,6 +327,8 @@ under the License.
<exclude>org.apache.maven.toolchain.DefaultToolchain#DefaultToolchain(org.apache.maven.toolchain.model.ToolchainModel,org.codehaus.plexus.logging.Logger):CONSTRUCTOR_REMOVED</exclude>
<exclude>org.apache.maven.toolchain.DefaultToolchain#DefaultToolchain(org.apache.maven.toolchain.model.ToolchainModel,java.lang.String,org.codehaus.plexus.logging.Logger):CONSTRUCTOR_REMOVED</exclude>
<exclude>org.apache.maven.toolchain.DefaultToolchainManager#logger</exclude>
<!-- Remove plexus utils -->
<exclude>org.apache.maven.project.ProjectSorter#getDAG():METHOD_REMOVED</exclude>
<!-- classes moved to maven-compat -->
<exclude>org.apache.maven.plugin.PluginManager</exclude>
<exclude>org.apache.maven.repository.ArtifactDoesNotExistException</exclude>

View File

@ -18,7 +18,7 @@
*/
package org.apache.maven;
import org.codehaus.plexus.util.dag.CycleDetectedException;
import org.apache.maven.project.CycleDetectedException;
/**
*/

View File

@ -25,10 +25,10 @@
import org.apache.maven.artifact.ArtifactUtils;
import org.apache.maven.plugin.descriptor.PluginDescriptor;
import org.apache.maven.project.CycleDetectedException;
import org.apache.maven.project.DuplicateProjectException;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.ProjectSorter;
import org.codehaus.plexus.util.dag.CycleDetectedException;
/**
* ReactorManager - unused

View File

@ -43,13 +43,13 @@
import org.apache.maven.model.Plugin;
import org.apache.maven.model.building.DefaultModelProblem;
import org.apache.maven.model.building.Result;
import org.apache.maven.project.CycleDetectedException;
import org.apache.maven.project.DuplicateProjectException;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.ProjectBuildingException;
import org.apache.maven.project.collector.MultiModuleCollectionStrategy;
import org.apache.maven.project.collector.PomlessCollectionStrategy;
import org.apache.maven.project.collector.RequestPomCollectionStrategy;
import org.codehaus.plexus.util.dag.CycleDetectedException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

View File

@ -31,10 +31,10 @@
import java.util.stream.Collectors;
import org.apache.maven.execution.ProjectDependencyGraph;
import org.apache.maven.project.CycleDetectedException;
import org.apache.maven.project.DuplicateProjectException;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.ProjectSorter;
import org.codehaus.plexus.util.dag.CycleDetectedException;
/**
* Describes the interdependencies between projects in the reactor.

View File

@ -0,0 +1,39 @@
/*
* 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.apache.maven.project;
import java.util.List;
public class CycleDetectedException extends Exception {
private final List<String> cycle;
public CycleDetectedException(String message, List<String> cycle) {
super(message);
this.cycle = cycle;
}
public List<String> getCycle() {
return cycle;
}
@Override
public String getMessage() {
return super.getMessage() + " " + String.join(" --> ", cycle);
}
}

View File

@ -0,0 +1,125 @@
/*
* 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.apache.maven.project;
import java.util.*;
class Graph {
private enum DfsState {
VISITING,
VISITED
}
final Map<String, Vertex> vertices = new LinkedHashMap<>();
public Vertex getVertex(String id) {
return vertices.get(id);
}
public Collection<Vertex> getVertices() {
return vertices.values();
}
Vertex addVertex(String label) {
return vertices.computeIfAbsent(label, Vertex::new);
}
void addEdge(Vertex from, Vertex to) throws CycleDetectedException {
from.children.add(to);
to.parents.add(from);
List<String> cycle = findCycle(to);
if (cycle != null) {
// remove edge which introduced cycle
removeEdge(from, to);
throw new CycleDetectedException(
"Edge between '" + from.label + "' and '" + to.label + "' introduces to cycle in the graph", cycle);
}
}
void removeEdge(Vertex from, Vertex to) {
from.children.remove(to);
to.parents.remove(from);
}
List<String> visitAll() {
return visitAll(vertices.values(), new HashMap<>(), new ArrayList<>());
}
List<String> findCycle(Vertex vertex) {
return visitCycle(Collections.singleton(vertex), new HashMap<>(), new LinkedList<>());
}
private static List<String> visitAll(
Collection<Vertex> children, Map<Vertex, DfsState> stateMap, List<String> list) {
for (Vertex v : children) {
DfsState state = stateMap.putIfAbsent(v, DfsState.VISITING);
if (state == null) {
visitAll(v.children, stateMap, list);
stateMap.put(v, DfsState.VISITED);
list.add(v.label);
}
}
return list;
}
private static List<String> visitCycle(
Collection<Vertex> children, Map<Vertex, DfsState> stateMap, LinkedList<String> cycle) {
for (Vertex v : children) {
DfsState state = stateMap.putIfAbsent(v, DfsState.VISITING);
if (state == null) {
cycle.addLast(v.label);
List<String> ret = visitCycle(v.children, stateMap, cycle);
if (ret != null) {
return ret;
}
cycle.removeLast();
stateMap.put(v, DfsState.VISITED);
} else if (state == DfsState.VISITING) {
// we are already visiting this vertex, this mean we have a cycle
int pos = cycle.lastIndexOf(v.label);
List<String> ret = cycle.subList(pos, cycle.size());
ret.add(v.label);
return ret;
}
}
return null;
}
static class Vertex {
final String label;
final List<Vertex> children = new ArrayList<>();
final List<Vertex> parents = new ArrayList<>();
Vertex(String label) {
this.label = label;
}
String getLabel() {
return label;
}
List<Vertex> getChildren() {
return children;
}
List<Vertex> getParents() {
return parents;
}
}
}

View File

@ -31,16 +31,13 @@
import org.apache.maven.api.model.Parent;
import org.apache.maven.api.model.Plugin;
import org.apache.maven.artifact.ArtifactUtils;
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.apache.maven.project.Graph.Vertex;
/**
* ProjectSorter
*/
public class ProjectSorter {
private DAG dag;
private Graph graph;
private List<MavenProject> sortedProjects;
@ -70,7 +67,7 @@ public class ProjectSorter {
// in a different lifecycle. Though the compiler-plugin has a valid use case, although
// that seems to work fine. We need to take versions and lifecycle into account.
public ProjectSorter(Collection<MavenProject> projects) throws CycleDetectedException, DuplicateProjectException {
dag = new DAG();
graph = new Graph();
// groupId:artifactId:version -> project
projectMap = new HashMap<>(projects.size() * 2);
@ -95,10 +92,10 @@ public ProjectSorter(Collection<MavenProject> projects) throws CycleDetectedExce
Map<String, Vertex> vertices = vertexMap.computeIfAbsent(projectKey, k -> new HashMap<>(2, 1));
vertices.put(project.getVersion(), dag.addVertex(projectId));
vertices.put(project.getVersion(), graph.addVertex(projectId));
}
for (Vertex projectVertex : dag.getVertices()) {
for (Vertex projectVertex : graph.getVertices()) {
String projectId = projectVertex.getLabel();
MavenProject project = projectMap.get(projectId);
@ -176,7 +173,7 @@ public ProjectSorter(Collection<MavenProject> projects) throws CycleDetectedExce
}
}
List<String> sortedProjectLabels = TopologicalSorter.sort(dag);
List<String> sortedProjectLabels = graph.visitAll();
this.sortedProjects = sortedProjectLabels.stream()
.map(id -> projectMap.get(id))
@ -231,11 +228,11 @@ private void addEdge(
}
if (force && toVertex.getChildren().contains(fromVertex)) {
dag.removeEdge(toVertex, fromVertex);
graph.removeEdge(toVertex, fromVertex);
}
try {
dag.addEdge(fromVertex, toVertex);
graph.addEdge(fromVertex, toVertex);
} catch (CycleDetectedException e) {
if (!safe) {
throw e;
@ -265,21 +262,17 @@ public boolean hasMultipleProjects() {
}
public List<String> getDependents(String id) {
return dag.getParentLabels(id);
return graph.getVertex(id).getParents().stream().map(Vertex::getLabel).collect(Collectors.toList());
}
public List<String> getDependencies(String id) {
return dag.getChildLabels(id);
return graph.getVertex(id).getChildren().stream().map(Vertex::getLabel).collect(Collectors.toList());
}
public static String getId(MavenProject project) {
return ArtifactUtils.key(project.getGroupId(), project.getArtifactId(), project.getVersion());
}
public DAG getDAG() {
return dag;
}
public Map<String, MavenProject> getProjectMap() {
return projectMap;
}

View File

@ -23,9 +23,9 @@
import org.apache.maven.execution.ProjectDependencyGraph;
import org.apache.maven.model.Dependency;
import org.apache.maven.project.CycleDetectedException;
import org.apache.maven.project.DuplicateProjectException;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.util.dag.CycleDetectedException;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;

View File

@ -45,12 +45,12 @@
import org.apache.maven.model.root.RootLocator;
import org.apache.maven.plugin.descriptor.MojoDescriptor;
import org.apache.maven.plugin.descriptor.PluginDescriptor;
import org.apache.maven.project.CycleDetectedException;
import org.apache.maven.project.DuplicateProjectException;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.MutablePlexusContainer;
import org.codehaus.plexus.PlexusContainer;
import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluator;
import org.codehaus.plexus.util.dag.CycleDetectedException;
import org.junit.jupiter.api.Test;
import static org.codehaus.plexus.testing.PlexusExtension.getTestFile;

View File

@ -48,12 +48,12 @@
import org.apache.maven.model.root.RootLocator;
import org.apache.maven.plugin.descriptor.MojoDescriptor;
import org.apache.maven.plugin.descriptor.PluginDescriptor;
import org.apache.maven.project.CycleDetectedException;
import org.apache.maven.project.DuplicateProjectException;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.MutablePlexusContainer;
import org.codehaus.plexus.PlexusContainer;
import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluator;
import org.codehaus.plexus.util.dag.CycleDetectedException;
import org.eclipse.aether.DefaultRepositorySystemSession;
import org.eclipse.aether.internal.impl.DefaultRepositorySystem;
import org.eclipse.aether.internal.impl.SimpleLocalRepositoryManagerFactory;

View File

@ -0,0 +1,233 @@
/*
* 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.apache.maven.project;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.maven.project.Graph.Vertex;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class GraphTest {
@Test
public void testGraph() throws CycleDetectedException {
Graph graph = new Graph();
graph.addVertex("a");
assertEquals(1, graph.getVertices().size());
assertEquals("a", graph.getVertex("a").getLabel());
graph.addVertex("a");
assertEquals(1, graph.getVertices().size());
assertEquals("a", graph.getVertex("a").getLabel());
graph.addVertex("b");
assertEquals(2, graph.getVertices().size());
assertFalse(hasEdge(graph, "a", "b"));
assertFalse(hasEdge(graph, "b", "a"));
Vertex a = graph.getVertex("a");
Vertex b = graph.getVertex("b");
assertEquals("a", a.getLabel());
assertEquals("b", b.getLabel());
addEdge(graph, "a", "b");
assertTrue(a.getChildren().contains(b));
assertTrue(b.getParents().contains(a));
assertTrue(hasEdge(graph, "a", "b"));
assertFalse(hasEdge(graph, "b", "a"));
addEdge(graph, "c", "d");
assertEquals(4, graph.getVertices().size());
Vertex c = graph.getVertex("c");
Vertex d = graph.getVertex("d");
assertEquals("a", a.getLabel());
assertEquals("b", b.getLabel());
assertEquals("c", c.getLabel());
assertEquals("d", d.getLabel());
assertFalse(hasEdge(graph, "b", "a"));
assertFalse(hasEdge(graph, "a", "c"));
assertFalse(hasEdge(graph, "a", "d"));
assertTrue(hasEdge(graph, "c", "d"));
assertFalse(hasEdge(graph, "d", "c"));
Set<String> labels = graph.getVertices().stream().map(Vertex::getLabel).collect(Collectors.toSet());
assertEquals(4, labels.size());
assertTrue(labels.contains("a"));
assertTrue(labels.contains("b"));
assertTrue(labels.contains("c"));
assertTrue(labels.contains("d"));
addEdge(graph, "a", "d");
assertEquals(2, a.getChildren().size());
assertTrue(a.getChildren().contains(b));
assertTrue(a.getChildren().contains(d));
assertEquals(2, d.getParents().size());
assertTrue(d.getParents().contains(a));
assertTrue(d.getParents().contains(c));
}
@Test
public void testCycleDetection() throws Exception {
Graph graph1 = new Graph();
addEdge(graph1, "a", "b");
addEdge(graph1, "b", "c");
Graph graph2 = new Graph();
addEdge(graph2, "a", "b");
addEdge(graph2, "b", "c");
CycleDetectedException cde = assertThrows(CycleDetectedException.class, () -> addEdge(graph2, "c", "a"));
List<String> cycle = cde.getCycle();
assertNotNull(cycle, "Cycle should be not null");
assertTrue(cycle.contains("a"), "Cycle contains 'a'");
assertTrue(cycle.contains("b"), "Cycle contains 'b'");
assertTrue(cycle.contains("c"), "Cycle contains 'c'");
Graph graph3 = new Graph();
addEdge(graph3, "a", "b");
addEdge(graph3, "b", "c");
addEdge(graph3, "b", "d");
addEdge(graph3, "a", "d");
Graph graph4 = new Graph();
addEdge(graph4, "a", "b");
addEdge(graph4, "b", "c");
addEdge(graph4, "b", "d");
addEdge(graph4, "a", "d");
cde = assertThrows(CycleDetectedException.class, () -> addEdge(graph4, "c", "a"));
assertEquals(Arrays.asList("a", "b", "c", "a"), cde.getCycle());
Graph graph5 = new Graph();
addEdge(graph5, "a", "b");
addEdge(graph5, "b", "c");
addEdge(graph5, "b", "f");
addEdge(graph5, "f", "g");
addEdge(graph5, "g", "h");
addEdge(graph5, "c", "d");
addEdge(graph5, "d", "e");
cde = assertThrows(CycleDetectedException.class, () -> addEdge(graph5, "e", "b"));
assertEquals(Arrays.asList("b", "c", "d", "e", "b"), cde.getCycle());
assertTrue(hasEdge(graph5, "a", "b"));
assertTrue(hasEdge(graph5, "b", "c"));
assertTrue(hasEdge(graph5, "b", "f"));
assertTrue(hasEdge(graph5, "f", "g"));
assertTrue(hasEdge(graph5, "g", "h"));
assertTrue(hasEdge(graph5, "c", "d"));
assertTrue(hasEdge(graph5, "d", "e"));
assertFalse(hasEdge(graph5, "e", "b"));
}
@Test
public void testDfs() throws CycleDetectedException {
Graph graph1 = new Graph();
addEdge(graph1, "a", "b");
addEdge(graph1, "b", "c");
List<String> expected1 = new ArrayList<>();
expected1.add("c");
expected1.add("b");
expected1.add("a");
List<String> actual1 = graph1.visitAll();
assertEquals(expected1, actual1);
Graph graph2 = new Graph();
graph2.addVertex("a");
graph2.addVertex("b");
graph2.addVertex("c");
addEdge(graph2, "b", "a");
addEdge(graph2, "c", "b");
List<String> expected2 = new ArrayList<>();
expected2.add("a");
expected2.add("b");
expected2.add("c");
List<String> actual2 = graph2.visitAll();
assertEquals(expected2, actual2);
Graph graph3 = new Graph();
graph3.addVertex("a");
graph3.addVertex("b");
graph3.addVertex("c");
graph3.addVertex("d");
graph3.addVertex("e");
graph3.addVertex("f");
addEdge(graph3, "a", "b");
addEdge(graph3, "b", "c");
addEdge(graph3, "b", "d");
addEdge(graph3, "c", "d");
addEdge(graph3, "c", "e");
addEdge(graph3, "f", "d");
addEdge(graph3, "e", "f");
addEdge(graph3, "f", "g");
List<String> expected3 = new ArrayList<>();
expected3.add("d");
expected3.add("g");
expected3.add("f");
expected3.add("e");
expected3.add("c");
expected3.add("b");
expected3.add("a");
List<String> actual3 = graph3.visitAll();
assertEquals(expected3, actual3);
Graph graph4 = new Graph();
graph4.addVertex("f");
graph4.addVertex("e");
graph4.addVertex("d");
graph4.addVertex("c");
graph4.addVertex("a");
graph4.addVertex("b");
addEdge(graph4, "a", "b");
addEdge(graph4, "b", "c");
addEdge(graph4, "b", "d");
addEdge(graph4, "c", "d");
addEdge(graph4, "c", "e");
addEdge(graph4, "f", "d");
addEdge(graph4, "e", "f");
List<String> expected4 = new ArrayList<>();
expected4.add("d");
expected4.add("f");
expected4.add("e");
expected4.add("c");
expected4.add("b");
expected4.add("a");
List<String> actual4 = graph4.visitAll();
assertEquals(expected4, actual4);
}
static void addEdge(Graph graph, String v1, String v2) throws CycleDetectedException {
Vertex vx1 = graph.addVertex(v1);
Vertex vx2 = graph.addVertex(v2);
graph.addEdge(vx1, vx2);
}
static boolean hasEdge(Graph graph, String v1, String v2) {
Vertex vx1 = graph.getVertex(v1);
Vertex vx2 = graph.getVertex(v2);
return vx1 != null && vx2 != null && vx1.children.contains(vx2);
}
}