This is an automated email from the ASF dual-hosted git repository.
martin_s pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/archiva-components.git
The following commit(s) were added to refs/heads/master by this push:
new d9378f8 Adding graph component
d9378f8 is described below
commit d9378f8a774e6b60257645968041d4fa4ddfc890
Author: Martin Stockhammer <[email protected]>
AuthorDate: Tue Nov 12 21:35:11 2019 +0100
Adding graph component
---
graph/pom.xml | 44 ++
.../apache/archiva/components/graph/api/Edge.java | 80 ++++
.../apache/archiva/components/graph/api/Graph.java | 50 ++
.../components/graph/api/TraversalStatus.java | 107 +++++
.../archiva/components/graph/api/Vertex.java | 70 +++
.../archiva/components/graph/api/VisitError.java | 38 ++
.../archiva/components/graph/base/BaseEdge.java | 97 ++++
.../components/graph/base/DirectedGraph.java | 91 ++++
.../archiva/components/graph/base/SimpleGraph.java | 66 +++
.../components/graph/base/SimpleVertex.java | 112 +++++
.../archiva/components/graph/util/Traversal.java | 202 +++++++++
.../components/graph/base/BaseEdgeTest.java | 108 +++++
.../components/graph/base/SimpleGraphTest.java | 70 +++
.../components/graph/base/SimpleVertexTest.java | 112 +++++
.../components/graph/util/TraversalTest.java | 505 +++++++++++++++++++++
graph/src/test/resources/log4j2-test.xml | 36 ++
pom.xml | 1 +
17 files changed, 1789 insertions(+)
diff --git a/graph/pom.xml b/graph/pom.xml
new file mode 100644
index 0000000..803b981
--- /dev/null
+++ b/graph/pom.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ 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.
+ -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <artifactId>archiva-components</artifactId>
+ <groupId>org.apache.archiva.components</groupId>
+ <version>3.0-SNAPSHOT</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>archiva-components-graph</artifactId>
+ <name>Archiva Components :: Graph</name>
+ <description>Simple implementation of graph structure and utility class for
traversal.</description>
+
+
+ <dependencies>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </dependency>
+ </dependencies>
+
+
+</project>
\ No newline at end of file
diff --git
a/graph/src/main/java/org/apache/archiva/components/graph/api/Edge.java
b/graph/src/main/java/org/apache/archiva/components/graph/api/Edge.java
new file mode 100644
index 0000000..5254ec8
--- /dev/null
+++ b/graph/src/main/java/org/apache/archiva/components/graph/api/Edge.java
@@ -0,0 +1,80 @@
+package org.apache.archiva.components.graph.api;
+/*
+ * 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.
+ */
+
+/**
+ * The edge links a source vertex to a destination vertex.
+ * A edge instance is always part of a certain graph instance.
+ *
+ * @param <V> The vertex implementation.
+ */
+public interface Edge<V extends Vertex<V>> {
+
+ /**
+ * Returns the identifier of this edge. The id must be unique for given
graph.
+ *
+ * @return the identifier of this edge
+ */
+ String getId();
+
+ /**
+ * Returns the label of this edge. The label must not be unique.
+ * If the label was not set, it should return an empty string.
+ * @return the label of this edge, or a empty string
+ */
+ String getLabel();
+
+ /**
+ * Sets the label of this edge to the given string.
+ * @param label the label string, that must not be <code>null</code>
+ * @throws NullPointerException if the label parameter is <code>null</code>
+ */
+ void setLabel(String label) throws NullPointerException;
+
+ /**
+ * Returns the graph where this edge was created.
+ *
+ * @return the graph instance
+ */
+ Graph<V> getGraph();
+
+ /**
+ * Returns the vertex, that is on the source end of this edge.
+ * @return the source vertex
+ */
+ V getSource();
+
+ /**
+ * Returns the vertex, that is on the destination end of this edge.
+ * @return the destination vertex
+ */
+ V getDestination();
+
+ /**
+ * Returns the weight of this edge. For standard graph implementations the
default should be 1.0, but
+ * graph implementations may decide to set another default.
+ *
+ * @return the weight of this edge
+ */
+ double getWeight();
+
+
+
+
+}
diff --git
a/graph/src/main/java/org/apache/archiva/components/graph/api/Graph.java
b/graph/src/main/java/org/apache/archiva/components/graph/api/Graph.java
new file mode 100644
index 0000000..13cdb90
--- /dev/null
+++ b/graph/src/main/java/org/apache/archiva/components/graph/api/Graph.java
@@ -0,0 +1,50 @@
+package org.apache.archiva.components.graph.api;
+/*
+ * 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.
+ */
+
+import org.apache.archiva.components.graph.base.SimpleVertex;
+
+import java.util.Set;
+
+/**
+ *
+ * A graph is the container for vertices. The vertices are connected by edges.
Each vertex and
+ * edge instance is coupled to the graph where it was created.
+ *
+ * @param <V>
+ */
+public interface Graph<V extends Vertex<V>> {
+
+ V addVertex(String label);
+
+ void removeVertex(V vertex);
+
+ Edge<V> addEdge(String label, V inVertex, V outVertex);
+
+ void removeEdge(Edge<SimpleVertex> edge);
+
+ V getVertex(String id);
+
+ Edge<V> getEdge(String id);
+
+ Set<V> getVertices();
+
+
+
+}
diff --git
a/graph/src/main/java/org/apache/archiva/components/graph/api/TraversalStatus.java
b/graph/src/main/java/org/apache/archiva/components/graph/api/TraversalStatus.java
new file mode 100644
index 0000000..eb589ce
--- /dev/null
+++
b/graph/src/main/java/org/apache/archiva/components/graph/api/TraversalStatus.java
@@ -0,0 +1,107 @@
+package org.apache.archiva.components.graph.api;
+/*
+ * 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.
+ */
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class gives information about the status of the graph traversal.
+ * It holds the errors encountered and information about detected cycles.
+ *
+ * @param <V>
+ */
+public class TraversalStatus<V extends Vertex<V>> {
+
+ private int cycles = 0;
+ private List<VisitError<V>> errorList;
+ private List<Vertex<V>> cycleVertices;
+
+ public TraversalStatus() {
+
+ }
+
+ /**
+ * Returns the list of errors
+ * @return a list of errors encountered while executing the consumer
function on vertices
+ */
+ public List<VisitError<V>> getErrorList() {
+ return errorList;
+ }
+
+ /**
+ * Returns true, if there were errors while running the consumer function.
+ * @return true, if errors occured on the consumer function, otherwise
false
+ */
+ public boolean hasErrors() {
+ return errorList!=null && errorList.size()>0;
+ }
+
+ /**
+ * Add the given error to the list.
+ *
+ * @param vertex the vertex, where the error occured
+ * @param e the exception
+ */
+ public void addError(V vertex, Throwable e) {
+ if (errorList==null) {
+ errorList = new ArrayList<>();
+ }
+ errorList.add(new VisitError(vertex, e));
+ }
+
+ /**
+ * Add another cycle to the counter
+ * @param vertex
+ */
+ public void registerCycle(Vertex<V> vertex) {
+ cycles++;
+ if (cycleVertices==null) {
+ cycleVertices = new ArrayList<>();
+ }
+ cycleVertices.add(vertex);
+ }
+
+ /**
+ * Returns true, if the traversal encountered a cycle.
+ * @return true, if cycle was encountered, otherwise false
+ */
+ public boolean hasCycles() {
+ return cycles>0;
+ }
+
+ /**
+ * Returns the number of detected cycles.
+ *
+ * @return
+ */
+ public int getCycleCount() {
+ return cycles;
+ }
+
+ /**
+ * Returns the vertices, where cycles were detected. The list may contain
+ * duplicated entries, if cycles where detected on different paths.
+ *
+ * @return the list of vertices where cycles were detected
+ */
+ public List<Vertex<V>> getCycleVertices() {
+ return cycleVertices;
+ }
+}
diff --git
a/graph/src/main/java/org/apache/archiva/components/graph/api/Vertex.java
b/graph/src/main/java/org/apache/archiva/components/graph/api/Vertex.java
new file mode 100644
index 0000000..d3937f3
--- /dev/null
+++ b/graph/src/main/java/org/apache/archiva/components/graph/api/Vertex.java
@@ -0,0 +1,70 @@
+package org.apache.archiva.components.graph.api;
+/*
+ * 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.
+ */
+
+import java.util.List;
+
+/**
+ * The vertex is a node in a graph structure. The vertex may have connections
to and from
+ * other vertices (edges).
+ *
+ * @param <V> The vertex implementation
+ */
+public interface Vertex<V extends Vertex<V>> {
+
+ /**
+ * Returns the identifier of this vertex. The identifier is unique for a
given graph.
+ * @return the identifier
+ */
+ String getId();
+
+ /**
+ * Returns the label of this vertex. The label is a name and must not be
unique.
+ * If the label was not set it should return a empty string.
+ * @return the label of the vertex or a empty string
+ */
+ String getLabel();
+
+ /**
+ * Sets the label of the vertex
+ * @param label sets the label string, must not be <code>null</code>
+ * @throws NullPointerException if the given label is <code>null</code>
+ */
+ void setLabel(String label) throws NullPointerException;
+
+ /**
+ * Returns the graph where this vertex was created.
+ * @return the graph instance
+ */
+ Graph<V> getGraph();
+
+ /**
+ * Returns all edges that connect to other nodes
+ * @return the edges where this vertex is the source
+ */
+ List<Edge<V>> getOutEdges();
+
+ /**
+ * Returns all edges that connect other nodes to this vertex
+ * @return the edges where this vertex is the destination
+ */
+ List<Edge<V>> getInEdges();
+
+
+}
diff --git
a/graph/src/main/java/org/apache/archiva/components/graph/api/VisitError.java
b/graph/src/main/java/org/apache/archiva/components/graph/api/VisitError.java
new file mode 100644
index 0000000..41f1c09
--- /dev/null
+++
b/graph/src/main/java/org/apache/archiva/components/graph/api/VisitError.java
@@ -0,0 +1,38 @@
+package org.apache.archiva.components.graph.api;
+/*
+ * 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.
+ */
+
+public class VisitError<V extends Vertex<V>> {
+
+ private final Throwable exception;
+ private final V vertex;
+
+ public VisitError(V vertex, Throwable exception) {
+ this.exception = exception;
+ this.vertex = vertex;
+ }
+
+ public Throwable getException() {
+ return exception;
+ }
+
+ public V getVertex() {
+ return vertex;
+ }
+}
diff --git
a/graph/src/main/java/org/apache/archiva/components/graph/base/BaseEdge.java
b/graph/src/main/java/org/apache/archiva/components/graph/base/BaseEdge.java
new file mode 100644
index 0000000..bdf274e
--- /dev/null
+++ b/graph/src/main/java/org/apache/archiva/components/graph/base/BaseEdge.java
@@ -0,0 +1,97 @@
+package org.apache.archiva.components.graph.base;
+/*
+ * 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.
+ */
+
+import org.apache.archiva.components.graph.api.Edge;
+import org.apache.archiva.components.graph.api.Graph;
+import org.apache.archiva.components.graph.api.Vertex;
+
+public class BaseEdge<V extends Vertex<V>> implements Edge<V>,
Comparable<BaseEdge<V>> {
+
+ private final String id;
+ private String label;
+ private double weight = 1.0;
+ private V sourceVertex;
+ private V destinationVertex;
+ private final Graph<V> graph;
+
+ public BaseEdge(Graph<V> graph, String id, V sourceVertex, V
destinationVertex) {
+ this.id = id;
+ this.graph = graph;
+ this.sourceVertex = sourceVertex;
+ this.destinationVertex = destinationVertex;
+ }
+
+ @Override
+ public String getId() {
+ return id;
+ }
+
+ @Override
+ public String getLabel() {
+ return label;
+ }
+
+ public void setLabel(String label) {
+ this.label = label;
+ }
+
+ public void setWeight(double weight) {
+ this.weight = weight;
+ }
+
+ @Override
+ public Graph<V> getGraph() {
+ return graph;
+ }
+
+ @Override
+ public V getSource() {
+ return sourceVertex;
+ }
+
+ @Override
+ public V getDestination() {
+ return destinationVertex;
+ }
+
+ @Override
+ public double getWeight() {
+ return weight;
+ }
+
+ @Override
+ public int hashCode() {
+ return this.id.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return this.id+"|"+this.label+": "+ sourceVertex.toString() + " -> " +
destinationVertex.toString();
+ }
+
+ @Override
+ public int compareTo(BaseEdge<V> o) {
+ if (this.label!=null || o.getLabel()!=null) {
+ return this.label.compareTo(o.getLabel());
+ } else {
+ return this.id.compareTo(o.getId());
+ }
+ }
+}
diff --git
a/graph/src/main/java/org/apache/archiva/components/graph/base/DirectedGraph.java
b/graph/src/main/java/org/apache/archiva/components/graph/base/DirectedGraph.java
new file mode 100644
index 0000000..eb946b1
--- /dev/null
+++
b/graph/src/main/java/org/apache/archiva/components/graph/base/DirectedGraph.java
@@ -0,0 +1,91 @@
+package org.apache.archiva.components.graph.base;
+/*
+ * 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.
+ */
+
+import org.apache.archiva.components.graph.api.Edge;
+import org.apache.archiva.components.graph.api.Graph;
+import org.apache.archiva.components.graph.api.Vertex;
+
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.stream.Collectors;
+
+
+/**
+ * Abstract implementation of a directed graph
+ * @param <V> The vertex implementation.
+ */
+public abstract class DirectedGraph<V extends Vertex<V>> implements Graph<V> {
+
+
+ protected TreeMap<String, V> vertices = new TreeMap<>();
+ protected TreeMap<String, Edge<V>> edges = new TreeMap<>();
+
+ public V addVertex(String label) {
+ V vtx = createNewVertex();
+ vtx.setLabel(label);
+ this.vertices.put(vtx.getId(), vtx);
+ return vtx;
+ }
+
+ /**
+ * Subclasses that implement this method must return a new vertex instance
for
+ * each call. The vertex should not be connected with any edges.
+ *
+ * @return A new vertex instance.
+ */
+ abstract V createNewVertex();
+
+ /**
+ * Subclasses that implement this method must return a new instance of the
edge with
+ * the given source and destination vertices. The vertices must be linked
by the new
+ * edge.
+ *
+ * @param sourceVertex the source vertex
+ * @param destinationVertex the destination vertex
+ * @return A new edge instance.
+ */
+ abstract Edge<V> createNewEdge(V sourceVertex, V destinationVertex);
+
+
+
+ @Override
+ public Edge<V> addEdge(String label, V sourceVertex, V destinationVertex) {
+ Edge<V> edge = createNewEdge(sourceVertex, destinationVertex);
+ edge.setLabel(label);
+ this.edges.put(edge.getId(), edge);
+ return edge;
+ }
+
+ @Override
+ public V getVertex(String id) {
+ return vertices.get(id);
+ }
+
+ @Override
+ public Edge<V> getEdge(String id) {
+ return edges.get(id);
+ }
+
+ @Override
+ public Set<V> getVertices() {
+ return vertices.values().stream().collect(Collectors.toSet());
+ }
+
+}
diff --git
a/graph/src/main/java/org/apache/archiva/components/graph/base/SimpleGraph.java
b/graph/src/main/java/org/apache/archiva/components/graph/base/SimpleGraph.java
new file mode 100644
index 0000000..9c51115
--- /dev/null
+++
b/graph/src/main/java/org/apache/archiva/components/graph/base/SimpleGraph.java
@@ -0,0 +1,66 @@
+package org.apache.archiva.components.graph.base;
+/*
+ * 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.
+ */
+
+import org.apache.archiva.components.graph.api.Edge;
+
+import java.util.UUID;
+
+/**
+ * Simple directed graph implementation that uses UUIDs as unique identifiers
for
+ * each vertex and edge.
+ *
+ *
+ */
+public class SimpleGraph extends DirectedGraph<SimpleVertex> {
+
+ @Override
+ SimpleVertex createNewVertex() {
+ return new SimpleVertex(this, UUID.randomUUID().toString());
+ }
+
+ @Override
+ Edge<SimpleVertex> createNewEdge(SimpleVertex sourceVertex, SimpleVertex
destinationVertex) {
+ BaseEdge edge = new BaseEdge<>(this, UUID.randomUUID().toString(),
sourceVertex, destinationVertex);
+ addEdgeToVertex(sourceVertex, edge);
+ addEdgeToVertex(destinationVertex, edge);
+ return edge;
+ }
+
+ private void addEdgeToVertex(SimpleVertex vertex, Edge<SimpleVertex> edge)
{
+ vertex.addEdge(edge);
+ }
+
+ @Override
+ public void removeEdge(Edge<SimpleVertex> edge) {
+ edges.remove(edge);
+ edge.getSource().removeEdge(edge);
+ edge.getDestination().removeEdge(edge);
+ }
+
+ @Override
+ public void removeVertex(SimpleVertex vertex) {
+ for (Edge<SimpleVertex> edge : vertex.getInEdges()) {
+ removeEdge(edge);
+ }
+ for (Edge<SimpleVertex> edge : vertex.getOutEdges()) {
+ removeEdge(edge);
+ }
+ }
+}
diff --git
a/graph/src/main/java/org/apache/archiva/components/graph/base/SimpleVertex.java
b/graph/src/main/java/org/apache/archiva/components/graph/base/SimpleVertex.java
new file mode 100644
index 0000000..4cd3aa2
--- /dev/null
+++
b/graph/src/main/java/org/apache/archiva/components/graph/base/SimpleVertex.java
@@ -0,0 +1,112 @@
+package org.apache.archiva.components.graph.base;
+/*
+ * 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.
+ */
+
+import org.apache.archiva.components.graph.api.Edge;
+import org.apache.archiva.components.graph.api.Graph;
+import org.apache.archiva.components.graph.api.Vertex;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Simple vertex implementation. The hash value is based on the id.
+ * Comparation is by label, if exists, otherwise by id.
+ */
+public class SimpleVertex implements Vertex<SimpleVertex>,
Comparable<SimpleVertex>
+{
+
+ private final String id;
+ private String label;
+ private final Graph<SimpleVertex> graph;
+ private List<Edge<SimpleVertex>> outEdges = new ArrayList<>();
+ private List<Edge<SimpleVertex>> inEdges = new ArrayList<>();
+
+ SimpleVertex(Graph<SimpleVertex> graph, String id) {
+ this.id = id;
+ this.graph = graph;
+ }
+
+ @Override
+ public String getId() {
+ return id;
+ }
+
+ @Override
+ public String getLabel() {
+ return label;
+ }
+
+ @Override
+ public void setLabel(String label) {
+ this.label = label;
+ }
+
+ @Override
+ public Graph<SimpleVertex> getGraph() {
+ return graph;
+ }
+
+ @Override
+ public List<Edge<SimpleVertex>> getOutEdges() {
+ return outEdges;
+ }
+
+ @Override
+ public List<Edge<SimpleVertex>> getInEdges() {
+ return inEdges;
+ }
+
+ protected void addEdge(Edge<SimpleVertex> edge) {
+ if (edge.getSource() == this) {
+ outEdges.add(edge);
+ } else if (edge.getDestination() == this) {
+ inEdges.add(edge);
+ } else {
+ throw new IllegalArgumentException("The given edge does not
contain this vertex");
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ return this.id.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return this.id+"|"+this.label;
+ }
+
+ public void removeEdge(Edge<SimpleVertex> edge) {
+ if (edge.getDestination()==this) {
+ inEdges.remove(edge);
+ } else if (edge.getSource()==this) {
+ outEdges.remove(edge);
+ }
+ }
+
+ @Override
+ public int compareTo(SimpleVertex o) {
+ if (label!=null && o.getLabel()!=null) {
+ return this.label.compareTo(o.getLabel());
+ } else {
+ return this.id.compareTo(o.getId());
+ }
+ }
+}
diff --git
a/graph/src/main/java/org/apache/archiva/components/graph/util/Traversal.java
b/graph/src/main/java/org/apache/archiva/components/graph/util/Traversal.java
new file mode 100644
index 0000000..e80f26a
--- /dev/null
+++
b/graph/src/main/java/org/apache/archiva/components/graph/util/Traversal.java
@@ -0,0 +1,202 @@
+package org.apache.archiva.components.graph.util;
+/*
+ * 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.
+ */
+
+import org.apache.archiva.components.graph.api.Edge;
+import org.apache.archiva.components.graph.api.TraversalStatus;
+import org.apache.archiva.components.graph.api.Vertex;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.*;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+/**
+ * Utility class for graph traversal.
+ */
+public class Traversal {
+
+ private static final Logger log = LoggerFactory.getLogger(Traversal.class);
+
+ /**
+ * Traverses the graph starting at the start node and using a depth first
algorithm.
+ * Each node will only be consumed once.
+ * The consumer function is applied for each visited node and must return
True, if the
+ * traversal should continue.
+ * If the directed flag is set to true, only outgoing edges are used for
traversal from one one
+ * to the other, otherwise, incoming edges are used too.
+ *
+ * @param start the start node
+ * @param consumer The consumer function. The function must return
<code>true</code>, if the traversal should
+ * continue, otherwise <code>false</code>
+ * @param directed If true, only outgoing edges are used to
navigate to neighbours, otherwise incoming and outgoing
+ * edges are used.
+ * @param continueOnError If true, the traversal continues, even if the
consumer function threw an error
+ * @param <V>
+ * @return The traversal status
+ */
+ @SuppressWarnings("Duplicates")
+ public static <V extends Vertex<V>> TraversalStatus<V> depthFirst(final V
start, final Function<V, Boolean> consumer,
+ final
boolean directed, final boolean continueOnError) {
+ TraversalStatus<V> status = new TraversalStatus<>();
+ Set<V> visited = new LinkedHashSet<>();
+ Stack<V> stack = new Stack<>();
+ stack.push(start);
+ while (!stack.isEmpty()) {
+ V vertex = stack.pop();
+ if (!visited.contains(vertex)) {
+ Boolean continueTraversal = Boolean.TRUE;
+ try {
+ continueTraversal = consumer.apply(vertex);
+ } catch (Throwable e) {
+ log.debug("Error during visit. Vertex: {}, Message: {}",
vertex, e.getMessage());
+ status.addError(vertex, e);
+ if (!continueOnError) {
+ break;
+ }
+ }
+ visited.add(vertex);
+ log.debug("Visited: " + vertex);
+ if (!continueTraversal) {
+ break;
+ }
+ // We traverse using the order of the edges. This is a stack,
so the elements that
+ // should be visited first are pushed last on the stack.
+ final List<Edge<V>> outEdges = vertex.getOutEdges();
+ final int outEdgeMaxIdx = vertex.getOutEdges().size() - 1;
+ for (int i = outEdgeMaxIdx; i >= 0; i--) {
+ try {
+ Edge<V> v = outEdges.get(i);
+ log.debug("Directed destination: " +
v.getDestination());
+ stack.push(v.getDestination());
+ } catch (IndexOutOfBoundsException e) {
+ log.warn("Modification of graph during traversal of
output edges: " + vertex + " Index: " + i);
+ }
+ }
+ if (!directed) {
+ final List<Edge<V>> inEdges = vertex.getInEdges();
+ final int inEdgeMaxIdx = vertex.getOutEdges().size() - 1;
+ for (int i = inEdgeMaxIdx; i >= 0; i--) {
+ try {
+ Edge<V> v = inEdges.get(i);
+ log.debug("Undirected source: " + v.getSource());
+ stack.push(v.getSource());
+ } catch (IndexOutOfBoundsException e) {
+ log.warn("Modification of graph during traversal
of input edges: " + vertex + " Index: " + i);
+ }
+ }
+ }
+ } else {
+ status.registerCycle(vertex);
+ }
+ }
+ return status;
+ }
+
+ /**
+ * Same as {@link #depthFirst(Vertex, Function, boolean, boolean)} but
sets the <code>continueOnError</code> parameter to <code>true</code>.
+ */
+ public static <V extends Vertex<V>> TraversalStatus<V> depthFirst(V start,
Function<V, Boolean> consumer,
+ boolean
directed) {
+ return depthFirst(start, consumer, directed, true);
+ }
+
+ /**
+ * Same as {@link #depthFirst(Vertex, Function, boolean, boolean)} but
sets the <code>continueOnError</code>
+ * parameter to <code>true</code> and <code>directed</code> parameter to
<code>true</code>.
+ */
+ public static <V extends Vertex<V>> TraversalStatus<V> depthFirst(V start,
Function<V, Boolean> consumer) {
+ return depthFirst(start, consumer, true, true);
+ }
+
+ /**
+ * Traverses the graph starting at the start node and using a breadth
first algorithm.
+ * Each node will only be consumed once.
+ * The consumer function is applied for each visited node and must return
True, if the
+ * traversal should continue.
+ * If the directed flag is set to true, only outgoing edges are used for
traversal from one one
+ * to the other, otherwise, incoming edges are used too.
+ *
+ * @param start the start node
+ * @param consumer The consumer function. The function must return
<code>true</code>, if the traversal should
+ * continue, otherwise <code>false</code>
+ * @param directed If true, only outgoing edges are used to
navigate to neighbours, otherwise incoming and outgoing
+ * edges are used.
+ * @param continueOnError If true, the traversal continues, even if the
consumer function threw an error
+ * @param <V>
+ * @return The traversal status
+ */
+ @SuppressWarnings("Duplicates")
+ public static <V extends Vertex<V>> TraversalStatus<V> breadthFirst(final
V start, final Function<V, Boolean> consumer,
+ final
boolean directed, final boolean continueOnError) {
+ final TraversalStatus<V> status = new TraversalStatus<>();
+ final Set<V> visited = new LinkedHashSet<>();
+ final Queue<V> queue = new LinkedList<>();
+ queue.add(start);
+ while (!queue.isEmpty()) {
+ V vertex = queue.poll();
+ if (!visited.contains(vertex)) {
+ Boolean continueTraversal = Boolean.TRUE;
+ try {
+ continueTraversal = consumer.apply(vertex);
+ } catch (Throwable e) {
+ log.debug("Error during visit. Vertex: {}, Message: {}",
vertex, e.getMessage());
+ status.addError(vertex, e);
+ if (!continueOnError) {
+ break;
+ }
+ }
+ visited.add(vertex);
+ log.debug("Visited: " + vertex);
+ if (!continueTraversal) {
+ break;
+ }
+ for (Edge<V> v : vertex.getOutEdges()) {
+ queue.add(v.getDestination());
+ }
+ if (!directed) {
+ for (Edge<V> v : vertex.getInEdges()) {
+ queue.add(v.getSource());
+ }
+ }
+ } else {
+ status.registerCycle(vertex);
+ }
+ }
+ return status;
+ }
+
+ /**
+ * Same as {@link #breadthFirst(Vertex, Function, boolean, boolean)} but
sets <code>continueOnError</code> to <code>true</code>.
+ */
+ public static <V extends Vertex<V>> TraversalStatus<V> breadthFirst(final
V start, final Function<V, Boolean> consumer,
+ final
boolean directed) {
+ return breadthFirst(start, consumer, directed, true);
+ }
+
+ /**
+ * Same as {@link #breadthFirst(Vertex, Function, boolean, boolean)} but
sets <code>continueOnError</code> to <code>true</code>
+ * and <code>directed</code> to <code>true</code>.
+ */
+ public static <V extends Vertex<V>> TraversalStatus<V> breadthFirst(final
V start, final Function<V, Boolean> consumer) {
+ return breadthFirst(start, consumer, true, true);
+ }
+
+}
diff --git
a/graph/src/test/java/org/apache/archiva/components/graph/base/BaseEdgeTest.java
b/graph/src/test/java/org/apache/archiva/components/graph/base/BaseEdgeTest.java
new file mode 100644
index 0000000..22f2e3d
--- /dev/null
+++
b/graph/src/test/java/org/apache/archiva/components/graph/base/BaseEdgeTest.java
@@ -0,0 +1,108 @@
+package org.apache.archiva.components.graph.base;
+/*
+ * 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.
+ */
+
+import org.apache.archiva.components.graph.api.Graph;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class BaseEdgeTest {
+
+
+ private Graph<SimpleVertex> graph = new SimpleGraph();
+
+ @Test
+ void getId() {
+ SimpleVertex vtx1 = new SimpleVertex(graph, "vtx1");
+ SimpleVertex vtx2 = new SimpleVertex(graph, "vtx2");
+
+ BaseEdge<SimpleVertex> edge = new BaseEdge<>(graph, "edge01", vtx1,
vtx2);
+
+ assertNotNull(edge.getId());
+ assertEquals("edge01", edge.getId());
+
+
+ }
+
+ @Test
+ void getLabel() {
+ SimpleVertex vtx1 = new SimpleVertex(graph, "vtx1");
+ SimpleVertex vtx2 = new SimpleVertex(graph, "vtx2");
+
+ BaseEdge<SimpleVertex> edge = new BaseEdge<>(graph, "edge01", vtx1,
vtx2);
+
+ assertNull(edge.getLabel());
+
+ edge.setLabel("edgelabel01");
+
+ assertEquals("edgelabel01", edge.getLabel());
+
+ edge.setLabel("Another label with exotic characters äÄö~§");
+ assertEquals("Another label with exotic characters äÄö~§",
edge.getLabel());
+
+ }
+
+ @Test
+ void setWeight() {
+ SimpleVertex vtx1 = new SimpleVertex(graph, "vtx1");
+ SimpleVertex vtx2 = new SimpleVertex(graph, "vtx2");
+
+ BaseEdge<SimpleVertex> edge = new BaseEdge<>(graph, "edge01", vtx1,
vtx2);
+
+ assertEquals(1.0, edge.getWeight());
+
+ edge.setWeight(1.5);
+ assertEquals(1.5, edge.getWeight());
+
+
+
+ }
+
+ @Test
+ void getGraph() {
+ SimpleVertex vtx1 = new SimpleVertex(graph, "vtx1");
+ SimpleVertex vtx2 = new SimpleVertex(graph, "vtx2");
+
+ BaseEdge<SimpleVertex> edge = new BaseEdge<>(graph, "edge01", vtx1,
vtx2);
+ assertNotNull(edge.getGraph());
+ assertTrue(edge.getGraph() == graph);
+ }
+
+ @Test
+ void getInVertex() {
+ SimpleVertex vtx1 = new SimpleVertex(graph, "vtx1");
+ SimpleVertex vtx2 = new SimpleVertex(graph, "vtx2");
+
+ BaseEdge<SimpleVertex> edge = new BaseEdge<>(graph, "edge01", vtx1,
vtx2);
+
+ assertEquals(vtx1, edge.getSource());
+ }
+
+ @Test
+ void getOutVertex() {
+ SimpleVertex vtx1 = new SimpleVertex(graph, "vtx1");
+ SimpleVertex vtx2 = new SimpleVertex(graph, "vtx2");
+
+ BaseEdge<SimpleVertex> edge = new BaseEdge<>(graph, "edge01", vtx1,
vtx2);
+
+ assertEquals(vtx2, edge.getDestination());
+ }
+
+}
\ No newline at end of file
diff --git
a/graph/src/test/java/org/apache/archiva/components/graph/base/SimpleGraphTest.java
b/graph/src/test/java/org/apache/archiva/components/graph/base/SimpleGraphTest.java
new file mode 100644
index 0000000..6455528
--- /dev/null
+++
b/graph/src/test/java/org/apache/archiva/components/graph/base/SimpleGraphTest.java
@@ -0,0 +1,70 @@
+package org.apache.archiva.components.graph.base;
+/*
+ * 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.
+ */
+
+import org.apache.archiva.components.graph.api.Edge;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class SimpleGraphTest {
+
+ @Test
+ void createNewVertex() {
+ SimpleGraph graph = new SimpleGraph();
+ SimpleVertex vtx = graph.createNewVertex();
+ assertNotNull(vtx);
+ assertNotNull(vtx.getId());
+ assertNotNull(java.util.UUID.fromString(vtx.getId()));
+ }
+
+ @Test
+ void createNewEdge() {
+ SimpleGraph graph = new SimpleGraph();
+ SimpleVertex vtx1 = graph.createNewVertex();
+ SimpleVertex vtx2 = graph.createNewVertex();
+ Edge<SimpleVertex> edge = graph.createNewEdge(vtx1, vtx2);
+ assertNotNull(edge);
+ assertNotNull(edge.getId());
+ assertNotNull(java.util.UUID.fromString(edge.getId()));
+ assertEquals(vtx1, edge.getSource());
+ assertEquals(vtx2, edge.getDestination());
+ }
+
+
+ @Test
+ void removeEdge() {
+ SimpleGraph graph = new SimpleGraph();
+ SimpleVertex vtx1 = graph.createNewVertex();
+ SimpleVertex vtx2 = graph.createNewVertex();
+ Edge<SimpleVertex> edge = graph.createNewEdge(vtx1, vtx2);
+
+ graph.removeEdge(edge);
+
+ assertEquals(0, vtx1.getInEdges().size());
+ assertEquals(0, vtx2.getInEdges().size());
+ assertEquals(0, vtx1.getOutEdges().size());
+ assertEquals(0, vtx2.getOutEdges().size());
+
+ }
+
+ @Test
+ void removeVertex() {
+ }
+}
\ No newline at end of file
diff --git
a/graph/src/test/java/org/apache/archiva/components/graph/base/SimpleVertexTest.java
b/graph/src/test/java/org/apache/archiva/components/graph/base/SimpleVertexTest.java
new file mode 100644
index 0000000..e468e48
--- /dev/null
+++
b/graph/src/test/java/org/apache/archiva/components/graph/base/SimpleVertexTest.java
@@ -0,0 +1,112 @@
+package org.apache.archiva.components.graph.base;
+/*
+ * 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.
+ */
+
+import org.apache.archiva.components.graph.api.Edge;
+import org.apache.archiva.components.graph.api.Graph;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class SimpleVertexTest {
+
+ private Graph<SimpleVertex> graph = new SimpleGraph();
+
+ @Test
+
+ void getId() {
+ SimpleVertex vtx = new SimpleVertex(graph, "testid001");
+ assertNotNull(vtx.getId());
+ assertEquals("testid001", vtx.getId());
+ }
+
+ @Test
+ void getLabel() {
+ SimpleVertex vtx = new SimpleVertex(graph, "testid001");
+ assertNull(vtx.getLabel());
+ vtx.setLabel("test");
+ assertEquals("test", vtx.getLabel());
+
+ vtx = new SimpleVertex(graph, "testid001");
+ vtx.setLabel("Another label with more characters üy@~ ---");
+ assertNotNull(vtx.getLabel());
+ assertEquals("Another label with more characters üy@~ ---",
vtx.getLabel());
+
+ }
+
+
+ @Test
+ void getGraph() {
+ SimpleVertex vtx = new SimpleVertex(graph, "test");
+ assertNotNull(vtx.getGraph());
+ assertTrue(vtx.getGraph() == graph);
+ }
+
+ @Test
+ void getInOutEdges() {
+ SimpleVertex vtx1 = new SimpleVertex(graph, "test1");
+ SimpleVertex vtx2 = new SimpleVertex(graph, "test2");
+ vtx1.addEdge(new BaseEdge<>(graph, "edge11_1to2", vtx1, vtx2));
+ vtx1.addEdge(new BaseEdge<>(graph, "edge12_1to2", vtx1, vtx2));
+ vtx1.addEdge(new BaseEdge<>(graph, "edge13_1to2", vtx1, vtx2));
+ vtx1.addEdge(new BaseEdge<>(graph, "edge14_1to2", vtx1, vtx2));
+ vtx2.addEdge(new BaseEdge<>(graph, "edge21_1to2", vtx1, vtx2));
+ vtx2.addEdge(new BaseEdge<>(graph, "edge22_1to2", vtx1, vtx2));
+ vtx1.addEdge(new BaseEdge<>(graph, "edge11_2to1", vtx2, vtx1));
+ vtx1.addEdge(new BaseEdge<>(graph, "edge12_2to1", vtx2, vtx1));
+ vtx1.addEdge(new BaseEdge<>(graph, "edge13_2to1", vtx2, vtx1));
+ vtx1.addEdge(new BaseEdge<>(graph, "edge14_2to1", vtx2, vtx1));
+ vtx1.addEdge(new BaseEdge<>(graph, "edge15_2to1", vtx2, vtx1));
+ vtx1.addEdge(new BaseEdge<>(graph, "edge16_2to1", vtx2, vtx1));
+ vtx2.addEdge(new BaseEdge<>(graph, "edge21_2to1", vtx2, vtx1));
+ assertEquals(6, vtx1.getInEdges().size());
+ assertEquals(2, vtx2.getInEdges().size());
+ assertEquals(4, vtx1.getOutEdges().size());
+ assertEquals(1, vtx2.getOutEdges().size());
+ }
+
+
+
+ @Test
+ void removeEdge() {
+ SimpleVertex vtx1 = new SimpleVertex(graph, "test1");
+ SimpleVertex vtx2 = new SimpleVertex(graph, "test2");
+ vtx1.addEdge(new BaseEdge<>(graph, "edge11_1to2", vtx1, vtx2));
+ vtx1.addEdge(new BaseEdge<>(graph, "edge12_1to2", vtx1, vtx2));
+ vtx1.addEdge(new BaseEdge<>(graph, "edge13_1to2", vtx1, vtx2));
+ vtx1.addEdge(new BaseEdge<>(graph, "edge14_1to2", vtx1, vtx2));
+ vtx2.addEdge(new BaseEdge<>(graph, "edge21_1to2", vtx1, vtx2));
+ vtx2.addEdge(new BaseEdge<>(graph, "edge22_1to2", vtx1, vtx2));
+ vtx1.addEdge(new BaseEdge<>(graph, "edge11_2to1", vtx2, vtx1));
+ vtx1.addEdge(new BaseEdge<>(graph, "edge12_2to1", vtx2, vtx1));
+ vtx1.addEdge(new BaseEdge<>(graph, "edge13_2to1", vtx2, vtx1));
+ vtx1.addEdge(new BaseEdge<>(graph, "edge14_2to1", vtx2, vtx1));
+ vtx1.addEdge(new BaseEdge<>(graph, "edge15_2to1", vtx2, vtx1));
+ vtx1.addEdge(new BaseEdge<>(graph, "edge16_2to1", vtx2, vtx1));
+ vtx2.addEdge(new BaseEdge<>(graph, "edge21_2to1", vtx2, vtx1));
+ Edge<SimpleVertex> edge = vtx1.getOutEdges().get(0);
+ vtx1.removeEdge(edge);
+ edge = vtx1.getInEdges().get(0);
+ vtx1.removeEdge(edge);
+ assertEquals(5, vtx1.getInEdges().size());
+ assertEquals(2, vtx2.getInEdges().size());
+ assertEquals(3, vtx1.getOutEdges().size());
+ assertEquals(1, vtx2.getOutEdges().size());
+ }
+}
\ No newline at end of file
diff --git
a/graph/src/test/java/org/apache/archiva/components/graph/util/TraversalTest.java
b/graph/src/test/java/org/apache/archiva/components/graph/util/TraversalTest.java
new file mode 100644
index 0000000..cbc26f1
--- /dev/null
+++
b/graph/src/test/java/org/apache/archiva/components/graph/util/TraversalTest.java
@@ -0,0 +1,505 @@
+package org.apache.archiva.components.graph.util;
+/*
+ * 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.
+ */
+
+import org.apache.archiva.components.graph.api.TraversalStatus;
+import org.apache.archiva.components.graph.base.SimpleGraph;
+import org.apache.archiva.components.graph.base.SimpleVertex;
+import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class TraversalTest {
+
+ private static final Logger log =
LoggerFactory.getLogger(TraversalTest.class);
+
+ @Test
+ void depthFirstWithoutCycleAndWithoutError() {
+ SimpleGraph graph = new SimpleGraph();
+ SimpleVertex vtx0 = graph.addVertex("root");
+ SimpleVertex vtx1 = graph.addVertex("vertex1");
+ SimpleVertex vtx11 = graph.addVertex("vertex11");
+ SimpleVertex vtx12 = graph.addVertex("vertex12");
+ SimpleVertex vtx2 = graph.addVertex("vertex2");
+ SimpleVertex vtx21 = graph.addVertex("vertex21");
+ SimpleVertex vtx211 = graph.addVertex("vertex211");
+ SimpleVertex vtx212 = graph.addVertex("vertex212");
+
+ SimpleVertex vtx22 = graph.addVertex("vertex22");
+ SimpleVertex vtx221 = graph.addVertex("vertex221");
+ SimpleVertex vtx222 = graph.addVertex("vertex222");
+ SimpleVertex vtx223 = graph.addVertex("vertex223");
+
+ SimpleVertex vtx3 = graph.addVertex("vertex3");
+ SimpleVertex vtx31 = graph.addVertex("vertex31");
+ SimpleVertex vtx32 = graph.addVertex("vertex32");
+ SimpleVertex vtx33 = graph.addVertex("vertex33");
+
+ graph.addEdge("root->1", vtx0, vtx1);
+ graph.addEdge("root->2", vtx0, vtx2);
+ graph.addEdge("root->3", vtx0, vtx3);
+
+ graph.addEdge("1->11", vtx1, vtx11);
+ graph.addEdge("1->12", vtx1, vtx12);
+
+ graph.addEdge("2->21", vtx2, vtx21);
+ graph.addEdge("2->22", vtx2, vtx22);
+
+ graph.addEdge("21->211", vtx21, vtx211);
+ graph.addEdge("21->212", vtx21, vtx212);
+
+ graph.addEdge("22->221", vtx22, vtx221);
+ graph.addEdge("22->222", vtx22, vtx222);
+ graph.addEdge("22->223", vtx22, vtx223);
+
+ graph.addEdge("3->31", vtx3, vtx31);
+ graph.addEdge("3->32", vtx3, vtx32);
+ graph.addEdge("3->33", vtx3, vtx33);
+
+ List<SimpleVertex> visitedNodes = new ArrayList<>();
+ TraversalStatus<SimpleVertex> status = Traversal.depthFirst(vtx0, v ->
{
+ log.debug("Visiting vertex " + v);
+ if (visitedNodes.contains(v)) {
+ throw new RuntimeException("Double visit of vertex: " + v);
+ }
+ visitedNodes.add(v);
+ return true;
+ }, true);
+
+ assertEquals(vtx0, visitedNodes.get(0));
+ assertEquals(vtx1, visitedNodes.get(1));
+ assertEquals(vtx11, visitedNodes.get(2));
+ assertEquals(vtx12, visitedNodes.get(3));
+ assertEquals(vtx2, visitedNodes.get(4));
+ assertEquals(vtx21, visitedNodes.get(5));
+ assertEquals(vtx211, visitedNodes.get(6));
+ assertEquals(vtx212, visitedNodes.get(7));
+ assertEquals(vtx22, visitedNodes.get(8));
+ assertEquals(vtx221, visitedNodes.get(9));
+ assertEquals(vtx222, visitedNodes.get(10));
+ assertEquals(vtx223, visitedNodes.get(11));
+ assertEquals(vtx3, visitedNodes.get(12));
+ assertEquals(vtx31, visitedNodes.get(13));
+ assertEquals(vtx32, visitedNodes.get(14));
+ assertEquals(vtx33, visitedNodes.get(15));
+
+ assertFalse(status.hasCycles());
+ assertFalse(status.hasErrors());
+ }
+
+ @Test
+ void depthFirstWithCycleAndWithoutError() {
+ SimpleGraph graph = new SimpleGraph();
+ SimpleVertex vtx0 = graph.addVertex("root");
+ SimpleVertex vtx1 = graph.addVertex("vertex1");
+ SimpleVertex vtx11 = graph.addVertex("vertex11");
+ SimpleVertex vtx12 = graph.addVertex("vertex12");
+ SimpleVertex vtx2 = graph.addVertex("vertex2");
+ SimpleVertex vtx21 = graph.addVertex("vertex21");
+ SimpleVertex vtx211 = graph.addVertex("vertex211");
+ SimpleVertex vtx212 = graph.addVertex("vertex212");
+
+ SimpleVertex vtx22 = graph.addVertex("vertex22");
+ SimpleVertex vtx221 = graph.addVertex("vertex221");
+ SimpleVertex vtx222 = graph.addVertex("vertex222");
+ SimpleVertex vtx223 = graph.addVertex("vertex223");
+
+ SimpleVertex vtx3 = graph.addVertex("vertex3");
+ SimpleVertex vtx31 = graph.addVertex("vertex31");
+ SimpleVertex vtx32 = graph.addVertex("vertex32");
+ SimpleVertex vtx33 = graph.addVertex("vertex33");
+
+ graph.addEdge("root->1", vtx0, vtx1);
+ graph.addEdge("root->2", vtx0, vtx2);
+ graph.addEdge("root->3", vtx0, vtx3);
+
+ graph.addEdge("1->11", vtx1, vtx11);
+ graph.addEdge("1->12", vtx1, vtx12);
+
+ graph.addEdge("2->21", vtx2, vtx21);
+ graph.addEdge("2->22", vtx2, vtx22);
+ graph.addEdge("22->2", vtx22, vtx2);
+
+ graph.addEdge("21->211", vtx21, vtx211);
+ graph.addEdge("21->212", vtx21, vtx212);
+
+ graph.addEdge("22->221", vtx22, vtx221);
+ graph.addEdge("22->222", vtx22, vtx222);
+ graph.addEdge("22->223", vtx22, vtx223);
+ graph.addEdge("223->root", vtx223, vtx0);
+
+ graph.addEdge("3->31", vtx3, vtx31);
+ graph.addEdge("3->32", vtx3, vtx32);
+ graph.addEdge("3->33", vtx3, vtx33);
+
+ List<SimpleVertex> visitedNodes = new ArrayList<>();
+ TraversalStatus<SimpleVertex> status = Traversal.depthFirst(vtx0, v ->
{
+ log.debug("Visiting vertex " + v);
+ if (visitedNodes.contains(v)) {
+ throw new RuntimeException("Double visit of vertex: " + v);
+ }
+ visitedNodes.add(v);
+ return true;
+ }, true);
+
+ assertEquals(vtx0, visitedNodes.get(0));
+ assertEquals(vtx1, visitedNodes.get(1));
+ assertEquals(vtx11, visitedNodes.get(2));
+ assertEquals(vtx12, visitedNodes.get(3));
+ assertEquals(vtx2, visitedNodes.get(4));
+ assertEquals(vtx21, visitedNodes.get(5));
+ assertEquals(vtx211, visitedNodes.get(6));
+ assertEquals(vtx212, visitedNodes.get(7));
+ assertEquals(vtx22, visitedNodes.get(8));
+ assertEquals(vtx221, visitedNodes.get(9));
+ assertEquals(vtx222, visitedNodes.get(10));
+ assertEquals(vtx223, visitedNodes.get(11));
+ assertEquals(vtx3, visitedNodes.get(12));
+ assertEquals(vtx31, visitedNodes.get(13));
+ assertEquals(vtx32, visitedNodes.get(14));
+ assertEquals(vtx33, visitedNodes.get(15));
+
+ assertTrue(status.hasCycles());
+ assertEquals(2, status.getCycleCount());
+ assertFalse(status.hasErrors());
+
+ }
+
+ @Test
+ void depthFirstWithCycleAndWithError() {
+ SimpleGraph graph = new SimpleGraph();
+ SimpleVertex vtx0 = graph.addVertex("root");
+ SimpleVertex vtx1 = graph.addVertex("vertex1");
+ SimpleVertex vtx11 = graph.addVertex("vertex11");
+ SimpleVertex vtx12 = graph.addVertex("vertex12");
+ SimpleVertex vtx2 = graph.addVertex("vertex2");
+ SimpleVertex vtx21 = graph.addVertex("vertex21");
+ SimpleVertex vtx211 = graph.addVertex("vertex211");
+ SimpleVertex vtx212 = graph.addVertex("vertex212");
+
+ SimpleVertex vtx22 = graph.addVertex("vertex22");
+ SimpleVertex vtx221 = graph.addVertex("vertex221");
+ SimpleVertex vtx222 = graph.addVertex("vertex222");
+ SimpleVertex vtx223 = graph.addVertex("vertex223");
+
+ SimpleVertex vtx3 = graph.addVertex("vertex3");
+ SimpleVertex vtx31 = graph.addVertex("vertex31");
+ SimpleVertex vtx32 = graph.addVertex("vertex32");
+ SimpleVertex vtx33 = graph.addVertex("vertex33");
+
+ graph.addEdge("root->1", vtx0, vtx1);
+ graph.addEdge("root->2", vtx0, vtx2);
+ graph.addEdge("root->3", vtx0, vtx3);
+
+ graph.addEdge("1->11", vtx1, vtx11);
+ graph.addEdge("1->12", vtx1, vtx12);
+
+ graph.addEdge("2->21", vtx2, vtx21);
+ graph.addEdge("2->22", vtx2, vtx22);
+ graph.addEdge("22->2", vtx22, vtx2);
+
+ graph.addEdge("21->211", vtx21, vtx211);
+ graph.addEdge("21->212", vtx21, vtx212);
+
+ graph.addEdge("22->221", vtx22, vtx221);
+ graph.addEdge("22->222", vtx22, vtx222);
+ graph.addEdge("22->223", vtx22, vtx223);
+ graph.addEdge("223->root", vtx223, vtx0);
+
+ graph.addEdge("3->31", vtx3, vtx31);
+ graph.addEdge("3->32", vtx3, vtx32);
+ graph.addEdge("3->33", vtx3, vtx33);
+
+ List<SimpleVertex> visitedNodes = new ArrayList<>();
+ TraversalStatus<SimpleVertex> status = Traversal.depthFirst(vtx0, v ->
{
+ log.debug("Visiting vertex " + v);
+ if (visitedNodes.contains(v)) {
+ throw new RuntimeException("Double visit of vertex: " + v);
+ }
+ visitedNodes.add(v);
+ if (v == vtx223 || v == vtx33 || v == vtx212) {
+ throw new RuntimeException("Error for node " + v);
+ }
+ return true;
+ }, true);
+
+ assertEquals(vtx0, visitedNodes.get(0));
+ assertEquals(vtx1, visitedNodes.get(1));
+ assertEquals(vtx11, visitedNodes.get(2));
+ assertEquals(vtx12, visitedNodes.get(3));
+ assertEquals(vtx2, visitedNodes.get(4));
+ assertEquals(vtx21, visitedNodes.get(5));
+ assertEquals(vtx211, visitedNodes.get(6));
+ assertEquals(vtx212, visitedNodes.get(7));
+ assertEquals(vtx22, visitedNodes.get(8));
+ assertEquals(vtx221, visitedNodes.get(9));
+ assertEquals(vtx222, visitedNodes.get(10));
+ assertEquals(vtx223, visitedNodes.get(11));
+ assertEquals(vtx3, visitedNodes.get(12));
+ assertEquals(vtx31, visitedNodes.get(13));
+ assertEquals(vtx32, visitedNodes.get(14));
+ assertEquals(vtx33, visitedNodes.get(15));
+
+ assertTrue(status.hasCycles());
+ assertEquals(2, status.getCycleCount());
+ assertTrue(status.hasErrors());
+ assertEquals(3, status.getErrorList().size());
+
assertTrue(status.getErrorList().get(0).getException().getMessage().startsWith("Error
for node"));
+
+ }
+
+ @Test
+ void breadthFirstWithoutCyclesAndWithoutError() {
+ SimpleGraph graph = new SimpleGraph();
+ SimpleVertex vtx0 = graph.addVertex("root");
+ SimpleVertex vtx1 = graph.addVertex("vertex1");
+ SimpleVertex vtx11 = graph.addVertex("vertex11");
+ SimpleVertex vtx12 = graph.addVertex("vertex12");
+ SimpleVertex vtx2 = graph.addVertex("vertex2");
+ SimpleVertex vtx21 = graph.addVertex("vertex21");
+ SimpleVertex vtx211 = graph.addVertex("vertex211");
+ SimpleVertex vtx212 = graph.addVertex("vertex212");
+
+ SimpleVertex vtx22 = graph.addVertex("vertex22");
+ SimpleVertex vtx221 = graph.addVertex("vertex221");
+ SimpleVertex vtx222 = graph.addVertex("vertex222");
+ SimpleVertex vtx223 = graph.addVertex("vertex223");
+
+ SimpleVertex vtx3 = graph.addVertex("vertex3");
+ SimpleVertex vtx31 = graph.addVertex("vertex31");
+ SimpleVertex vtx32 = graph.addVertex("vertex32");
+ SimpleVertex vtx33 = graph.addVertex("vertex33");
+
+ graph.addEdge("root->1", vtx0, vtx1);
+ graph.addEdge("root->2", vtx0, vtx2);
+ graph.addEdge("root->3", vtx0, vtx3);
+
+ graph.addEdge("1->11", vtx1, vtx11);
+ graph.addEdge("1->12", vtx1, vtx12);
+
+ graph.addEdge("2->21", vtx2, vtx21);
+ graph.addEdge("2->22", vtx2, vtx22);
+
+ graph.addEdge("21->211", vtx21, vtx211);
+ graph.addEdge("21->212", vtx21, vtx212);
+
+ graph.addEdge("22->221", vtx22, vtx221);
+ graph.addEdge("22->222", vtx22, vtx222);
+ graph.addEdge("22->223", vtx22, vtx223);
+
+ graph.addEdge("3->31", vtx3, vtx31);
+ graph.addEdge("3->32", vtx3, vtx32);
+ graph.addEdge("3->33", vtx3, vtx33);
+
+ List<SimpleVertex> visitedNodes = new ArrayList<>();
+ TraversalStatus<SimpleVertex> status = Traversal.breadthFirst(vtx0, v
-> {
+ log.debug("Visiting vertex " + v);
+ if (visitedNodes.contains(v)) {
+ throw new RuntimeException("Double visit of vertex: " + v);
+ }
+ visitedNodes.add(v);
+ return true;
+ }, true);
+
+ assertEquals(vtx0, visitedNodes.get(0));
+ assertEquals(vtx1, visitedNodes.get(1));
+ assertEquals(vtx2, visitedNodes.get(2));
+ assertEquals(vtx3, visitedNodes.get(3));
+ assertEquals(vtx11, visitedNodes.get(4));
+ assertEquals(vtx12, visitedNodes.get(5));
+ assertEquals(vtx21, visitedNodes.get(6));
+ assertEquals(vtx22, visitedNodes.get(7));
+ assertEquals(vtx31, visitedNodes.get(8));
+ assertEquals(vtx32, visitedNodes.get(9));
+ assertEquals(vtx33, visitedNodes.get(10));
+ assertEquals(vtx211, visitedNodes.get(11));
+ assertEquals(vtx212, visitedNodes.get(12));
+ assertEquals(vtx221, visitedNodes.get(13));
+ assertEquals(vtx222, visitedNodes.get(14));
+ assertEquals(vtx223, visitedNodes.get(15));
+
+ assertFalse(status.hasCycles());
+ assertFalse(status.hasErrors());
+ }
+
+ @Test
+ void breadthFirstWithCyclesAndWithoutError() {
+ SimpleGraph graph = new SimpleGraph();
+ SimpleVertex vtx0 = graph.addVertex("root");
+ SimpleVertex vtx1 = graph.addVertex("vertex1");
+ SimpleVertex vtx11 = graph.addVertex("vertex11");
+ SimpleVertex vtx12 = graph.addVertex("vertex12");
+ SimpleVertex vtx2 = graph.addVertex("vertex2");
+ SimpleVertex vtx21 = graph.addVertex("vertex21");
+ SimpleVertex vtx211 = graph.addVertex("vertex211");
+ SimpleVertex vtx212 = graph.addVertex("vertex212");
+
+ SimpleVertex vtx22 = graph.addVertex("vertex22");
+ SimpleVertex vtx221 = graph.addVertex("vertex221");
+ SimpleVertex vtx222 = graph.addVertex("vertex222");
+ SimpleVertex vtx223 = graph.addVertex("vertex223");
+
+ SimpleVertex vtx3 = graph.addVertex("vertex3");
+ SimpleVertex vtx31 = graph.addVertex("vertex31");
+ SimpleVertex vtx32 = graph.addVertex("vertex32");
+ SimpleVertex vtx33 = graph.addVertex("vertex33");
+
+ graph.addEdge("root->1", vtx0, vtx1);
+ graph.addEdge("root->2", vtx0, vtx2);
+ graph.addEdge("root->3", vtx0, vtx3);
+
+ graph.addEdge("1->11", vtx1, vtx11);
+ graph.addEdge("1->12", vtx1, vtx12);
+
+ graph.addEdge("2->21", vtx2, vtx21);
+ graph.addEdge("2->22", vtx2, vtx22);
+
+ graph.addEdge("21->211", vtx21, vtx211);
+ graph.addEdge("21->212", vtx21, vtx212);
+ graph.addEdge("211->21", vtx211, vtx21);
+
+ graph.addEdge("22->221", vtx22, vtx221);
+ graph.addEdge("22->222", vtx22, vtx222);
+ graph.addEdge("22->223", vtx22, vtx223);
+ graph.addEdge("223->root", vtx223, vtx0);
+
+ graph.addEdge("3->31", vtx3, vtx31);
+ graph.addEdge("3->32", vtx3, vtx32);
+ graph.addEdge("3->33", vtx3, vtx33);
+
+ List<SimpleVertex> visitedNodes = new ArrayList<>();
+ TraversalStatus<SimpleVertex> status = Traversal.breadthFirst(vtx0, v
-> {
+ log.debug("Visiting vertex " + v);
+ if (visitedNodes.contains(v)) {
+ throw new RuntimeException("Double visit of vertex: " + v);
+ }
+ visitedNodes.add(v);
+ return true;
+ }, true);
+
+ assertEquals(vtx0, visitedNodes.get(0));
+ assertEquals(vtx1, visitedNodes.get(1));
+ assertEquals(vtx2, visitedNodes.get(2));
+ assertEquals(vtx3, visitedNodes.get(3));
+ assertEquals(vtx11, visitedNodes.get(4));
+ assertEquals(vtx12, visitedNodes.get(5));
+ assertEquals(vtx21, visitedNodes.get(6));
+ assertEquals(vtx22, visitedNodes.get(7));
+ assertEquals(vtx31, visitedNodes.get(8));
+ assertEquals(vtx32, visitedNodes.get(9));
+ assertEquals(vtx33, visitedNodes.get(10));
+ assertEquals(vtx211, visitedNodes.get(11));
+ assertEquals(vtx212, visitedNodes.get(12));
+ assertEquals(vtx221, visitedNodes.get(13));
+ assertEquals(vtx222, visitedNodes.get(14));
+ assertEquals(vtx223, visitedNodes.get(15));
+
+ assertTrue(status.hasCycles());
+ assertEquals(2, status.getCycleCount());
+ assertFalse(status.hasErrors());
+ }
+
+ @Test
+ void breadthFirstWithCyclesAndWithError() {
+ SimpleGraph graph = new SimpleGraph();
+ SimpleVertex vtx0 = graph.addVertex("root");
+ SimpleVertex vtx1 = graph.addVertex("vertex1");
+ SimpleVertex vtx11 = graph.addVertex("vertex11");
+ SimpleVertex vtx12 = graph.addVertex("vertex12");
+ SimpleVertex vtx2 = graph.addVertex("vertex2");
+ SimpleVertex vtx21 = graph.addVertex("vertex21");
+ SimpleVertex vtx211 = graph.addVertex("vertex211");
+ SimpleVertex vtx212 = graph.addVertex("vertex212");
+
+ SimpleVertex vtx22 = graph.addVertex("vertex22");
+ SimpleVertex vtx221 = graph.addVertex("vertex221");
+ SimpleVertex vtx222 = graph.addVertex("vertex222");
+ SimpleVertex vtx223 = graph.addVertex("vertex223");
+
+ SimpleVertex vtx3 = graph.addVertex("vertex3");
+ SimpleVertex vtx31 = graph.addVertex("vertex31");
+ SimpleVertex vtx32 = graph.addVertex("vertex32");
+ SimpleVertex vtx33 = graph.addVertex("vertex33");
+
+ graph.addEdge("root->1", vtx0, vtx1);
+ graph.addEdge("root->2", vtx0, vtx2);
+ graph.addEdge("root->3", vtx0, vtx3);
+
+ graph.addEdge("1->11", vtx1, vtx11);
+ graph.addEdge("1->12", vtx1, vtx12);
+
+ graph.addEdge("2->21", vtx2, vtx21);
+ graph.addEdge("2->22", vtx2, vtx22);
+
+ graph.addEdge("21->211", vtx21, vtx211);
+ graph.addEdge("21->212", vtx21, vtx212);
+ graph.addEdge("211->21", vtx211, vtx21);
+
+ graph.addEdge("22->221", vtx22, vtx221);
+ graph.addEdge("22->222", vtx22, vtx222);
+ graph.addEdge("22->223", vtx22, vtx223);
+ graph.addEdge("223->root", vtx223, vtx0);
+
+ graph.addEdge("3->31", vtx3, vtx31);
+ graph.addEdge("3->32", vtx3, vtx32);
+ graph.addEdge("3->33", vtx3, vtx33);
+
+ List<SimpleVertex> visitedNodes = new ArrayList<>();
+ TraversalStatus<SimpleVertex> status = Traversal.breadthFirst(vtx0, v
-> {
+ log.debug("Visiting vertex " + v);
+ if (visitedNodes.contains(v)) {
+ throw new RuntimeException("Double visit of vertex: " + v);
+ }
+ visitedNodes.add(v);
+ if (v == vtx212 || v == vtx31 || v == vtx21) {
+ throw new RuntimeException("Error on node: " + v);
+ }
+ return true;
+ }, true);
+
+ assertEquals(vtx0, visitedNodes.get(0));
+ assertEquals(vtx1, visitedNodes.get(1));
+ assertEquals(vtx2, visitedNodes.get(2));
+ assertEquals(vtx3, visitedNodes.get(3));
+ assertEquals(vtx11, visitedNodes.get(4));
+ assertEquals(vtx12, visitedNodes.get(5));
+ assertEquals(vtx21, visitedNodes.get(6));
+ assertEquals(vtx22, visitedNodes.get(7));
+ assertEquals(vtx31, visitedNodes.get(8));
+ assertEquals(vtx32, visitedNodes.get(9));
+ assertEquals(vtx33, visitedNodes.get(10));
+ assertEquals(vtx211, visitedNodes.get(11));
+ assertEquals(vtx212, visitedNodes.get(12));
+ assertEquals(vtx221, visitedNodes.get(13));
+ assertEquals(vtx222, visitedNodes.get(14));
+ assertEquals(vtx223, visitedNodes.get(15));
+
+ assertTrue(status.hasCycles());
+ assertEquals(2, status.getCycleCount());
+ assertTrue(status.hasErrors());
+ assertEquals(3, status.getErrorList().size());
+
assertTrue(status.getErrorList().get(0).getException().getMessage().startsWith("Error
on node"));
+ }
+
+}
\ No newline at end of file
diff --git a/graph/src/test/resources/log4j2-test.xml
b/graph/src/test/resources/log4j2-test.xml
new file mode 100644
index 0000000..4ed4e77
--- /dev/null
+++ b/graph/src/test/resources/log4j2-test.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!--
+ ~ 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.
+ -->
+<configuration>
+ <appenders>
+ <Console name="console" target="SYSTEM_OUT">
+ <PatternLayout pattern="[%t] %-5p %c %x - %m%n"/>
+ </Console>
+ </appenders>
+ <loggers>
+ <logger name="org.apache.archiva" level="info"/>
+ <logger name="org.apache.archiva.components.graph" level="info" />
+
+ <root level="error" includeLocation="true">
+ <appender-ref ref="console"/>
+ </root>
+ </loggers>
+</configuration>
+
+
diff --git a/pom.xml b/pom.xml
index a91b450..9ebe160 100644
--- a/pom.xml
+++ b/pom.xml
@@ -47,6 +47,7 @@
<modules>
<module>expression-evaluator</module>
<module>spring-apacheds</module>
+ <module>graph</module>
</modules>
<scm>