getSkippedEdge() {
+ return skippedEdge;
+ }
+
+ public static int[] reverse(int[] sequence) {
+
+ //
+ checkNotNull(sequence);
+
+ //
+ int[] result = new int[sequence.length];
+ for (int i = 0; i < sequence.length; i++) {
+ result[sequence.length - (1 + i)] = sequence[i];
+ }
+
+ //
+ return result;
+ }
+
+ /**
+ *
+ * Tries to find and remove a sink.
+ *
+ *
+ * @return true
if a sink was found and removed.
+ */
+ private boolean findSink() {
+
+ // initialize the sink
+ int sink = -1;
+
+ // try to find a sink...
+ for (Integer i : vertices) {
+ sink = i;
+ for (Integer j : vertices) {
+ if (i != j && this.adjacencyMatrix[i][j] != 0) {
+ sink = -1;
+ break;
+ }
+ }
+ if (sink != -1) {
+ break;
+ }
+ }
+
+ // if a sink was found, remove it and return true...
+ if (sink != -1) {
+ vertices.remove(sink);
+ s2.add(0, sink);
+ return true;
+ }
+ // ...otherwise return false
+ else {
+ return false;
+ }
+ }
+
+ /**
+ *
+ * Tries to find and remove a source.
+ *
+ *
+ * @return true
if a source was found and removed.
+ */
+ private boolean findSource() {
+
+ // initialize the source
+ int source = -1;
+
+ // try to find a source...
+ for (Integer i : vertices) {
+ source = i;
+ for (Integer j : vertices) {
+ if (i != j && this.adjacencyMatrix[j][i] != 0) {
+ source = -1;
+ break;
+ }
+ }
+ if (source != -1) {
+ break;
+ }
+ }
+
+ // if a source was found, remove it and return true...
+ if (source != -1) {
+ vertices.remove(source);
+ s1.add(source);
+ return true;
+ }
+ // ...otherwise return false
+ else {
+ return false;
+ }
+ }
+
+ /**
+ *
+ *
+ *
+ * @return
+ */
+ private boolean findVertexToRemove() {
+
+ // initialize current maximum and vertex
+ int currentMaximum = Integer.MIN_VALUE;
+ int currentVertex = Integer.MIN_VALUE;
+
+ // find the vertex with the highest maximum
+ for (Integer vertex : vertices) {
+ int delta = getDelta(vertex);
+ if (currentVertex == Integer.MIN_VALUE || currentMaximum < delta) {
+ currentMaximum = delta;
+ currentVertex = vertex;
+ }
+ }
+
+ // remove vertex and return true...
+ vertices.remove(currentVertex);
+
+ //
+ for (Integer j : vertices) {
+ if (currentVertex != j && this.adjacencyMatrix[j][currentVertex] != 0) {
+ skippedEdge.add(new Integer[] { j, currentVertex });
+ }
+ }
+
+ s1.add(currentVertex);
+ return false;
+ }
+
+ /**
+ *
+ *
+ *
+ * @param vertex
+ * @return
+ */
+ private int getDelta(int vertex) {
+
+ int in = 0;
+ int out = 0;
+
+ for (Integer j : vertices) {
+ if (vertex != j) {
+ in = in + this.adjacencyMatrix[j][vertex];
+ out = out + this.adjacencyMatrix[vertex][j];
+ }
+ }
+
+ //
+ return out - in;
+ }
+
+ /**
+ *
+ * Helper method. Concatenates the given lists and returns them as one array.
+ *
+ *
+ * @param s1 the list s1
+ * @param s2 the list s2
+ * @return the result array.
+ */
+ private int[] convertToArray(List s1, List s2) {
+ int[] result = new int[s1.size() + s2.size()];
+ int index = 0;
+ for (int i : s1) {
+ result[index] = i;
+ index++;
+ }
+ for (int i : s2) {
+ result[index] = i;
+ index++;
+ }
+ return result;
+ }
+}
diff --git a/javac-plugin/src/main/java/org/moditect/deptective/internal/graph/impl/FastFasSorter.java b/javac-plugin/src/main/java/org/moditect/deptective/internal/graph/impl/FastFasSorter.java
new file mode 100644
index 0000000..cdc254f
--- /dev/null
+++ b/javac-plugin/src/main/java/org/moditect/deptective/internal/graph/impl/FastFasSorter.java
@@ -0,0 +1,92 @@
+/**
+ * Copyright 2019 The ModiTect authors
+ *
+ * Licensed 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.moditect.deptective.internal.graph.impl;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.moditect.deptective.internal.graph.GraphUtils;
+import org.moditect.deptective.internal.graph.Dependency;
+import org.moditect.deptective.internal.graph.Node;
+import org.moditect.deptective.internal.graph.INodeSorter;
+
+public class FastFasSorter implements INodeSorter {
+
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ @Override
+ public SortResult sort(List artifacts) {
+
+ // we have to compute the adjacency matrix first
+ int[][] adjacencyMatrix = GraphUtils.computeAdjacencyMatrix(artifacts);
+
+ // the ordered sequence (highest first!)
+ FastFAS fastFAS = new FastFAS(adjacencyMatrix);
+ int[] ordered = fastFAS.getOrderedSequence();
+
+ // Bubbles
+ for (int outerIndex = 1; outerIndex < ordered.length; outerIndex++) {
+ for (int index = outerIndex; index >= 1; index--) {
+
+ //
+ if (adjacencyMatrix[ordered[index]][ordered[index
+ - 1]] > adjacencyMatrix[ordered[index - 1]][ordered[index]]) {
+
+ // swap...
+ int temp = ordered[index];
+ ordered[index] = ordered[index - 1];
+ ordered[index - 1] = temp;
+
+ }
+ else {
+
+ // stop bubbling...
+ break;
+ }
+ }
+ }
+
+ // reverse it
+ ordered = FastFAS.reverse(ordered);
+
+ // create the result nodes list
+ List resultNodes = new ArrayList<>(artifacts.size());
+ for (int index : ordered) {
+ resultNodes.add(artifacts.get(index));
+ }
+
+ // create the list of upwards dependencies
+ List upwardsDependencies = new ArrayList<>();
+ for (Integer[] values : fastFAS.getSkippedEdge()) {
+ Node source = artifacts.get(values[0]);
+ Node target = artifacts.get(values[1]);
+ upwardsDependencies.add(source.getOutgoingDependencyTo(target));
+ }
+
+ // return the result
+ return new SortResult() {
+
+ @Override
+ public List getOrderedNodes() {
+ return resultNodes;
+ }
+
+ @Override
+ public List getUpwardsDependencies() {
+ return upwardsDependencies;
+ }
+ };
+ }
+}
diff --git a/javac-plugin/src/main/java/org/moditect/deptective/internal/graph/impl/Tarjan.java b/javac-plugin/src/main/java/org/moditect/deptective/internal/graph/impl/Tarjan.java
new file mode 100644
index 0000000..faaa28d
--- /dev/null
+++ b/javac-plugin/src/main/java/org/moditect/deptective/internal/graph/impl/Tarjan.java
@@ -0,0 +1,97 @@
+/**
+ * Copyright 2019 The ModiTect authors
+ *
+ * Licensed 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.moditect.deptective.internal.graph.impl;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.moditect.deptective.internal.graph.GraphUtils;
+import org.moditect.deptective.internal.graph.Node;
+
+public class Tarjan {
+
+ private int _index = 0;
+ private ArrayList _stack = new ArrayList();
+ private List> _stronglyConnectedComponents = new ArrayList>();
+ int[] _vlowlink;
+ int[] _vindex;
+
+ private Node[] _artifacts;
+
+ public List> detectStronglyConnectedComponents(Collection extends T> artifacts) {
+ checkNotNull(artifacts);
+
+ _artifacts = artifacts.toArray(new Node[0]);
+ int[][] adjacencyList = GraphUtils.computeAdjacencyList(_artifacts);
+ return executeTarjan(adjacencyList);
+ }
+
+ private List> executeTarjan(int[][] graph) {
+ checkNotNull(graph);
+
+ _stronglyConnectedComponents.clear();
+ _index = 0;
+ _stack.clear();
+ _vlowlink = new int[graph.length];
+ _vindex = new int[graph.length];
+ for (int i = 0; i < _vlowlink.length; i++) {
+ _vlowlink[i] = -1;
+ _vindex[i] = -1;
+ }
+
+ for (int i = 0; i < graph.length; i++) {
+ if (_vindex[i] == -1) {
+ tarjan(i, graph);
+ }
+ }
+
+ return _stronglyConnectedComponents;
+ }
+
+ @SuppressWarnings("unchecked")
+ private void tarjan(int v, int[][] graph) {
+ checkNotNull(v);
+ checkNotNull(graph);
+
+ _vindex[v] = _index;
+ _vlowlink[v] = _index;
+
+ _index++;
+ _stack.add(0, v);
+ for (int n : graph[v]) {
+ if (_vindex[n] == -1) {
+ tarjan(n, graph);
+ _vlowlink[v] = Math.min(_vlowlink[v], _vlowlink[n]);
+ }
+ else if (_stack.contains(n)) {
+ _vlowlink[v] = Math.min(_vlowlink[v], _vindex[n]);
+ }
+ }
+ if (_vlowlink[v] == _vindex[v]) {
+ int n;
+ ArrayList component = new ArrayList();
+ do {
+ n = _stack.remove(0);
+ component.add((T) _artifacts[n]);
+ }
+ while (n != v);
+ _stronglyConnectedComponents.add(component);
+ }
+ }
+}
diff --git a/javac-plugin/src/test/java/org/moditect/deptective/internal/graph/CyclesTest.java b/javac-plugin/src/test/java/org/moditect/deptective/internal/graph/CyclesTest.java
new file mode 100644
index 0000000..dea2928
--- /dev/null
+++ b/javac-plugin/src/test/java/org/moditect/deptective/internal/graph/CyclesTest.java
@@ -0,0 +1,45 @@
+/**
+ * Copyright 2019 The ModiTect authors
+ *
+ * Licensed 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.moditect.deptective.internal.graph;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.List;
+
+import org.junit.Test;
+
+/**
+ *
+ * @author Gerd Wütherich (gw@code-kontor.io)
+ */
+public class CyclesTest {
+
+ @Test
+ public void detectCycle() {
+
+ //
+ List nodes = TestModelCreator.createDummyModel();
+
+ //
+ List> cycles = GraphUtils.detectCycles(nodes);
+
+ //
+ assertThat(cycles).hasSize(1);
+
+ //
+ assertThat(cycles.get(0)).contains(nodes.get(2)).contains(nodes.get(3));
+ }
+}
diff --git a/javac-plugin/src/test/java/org/moditect/deptective/internal/graph/DependencyStructureMatrixTest.java b/javac-plugin/src/test/java/org/moditect/deptective/internal/graph/DependencyStructureMatrixTest.java
new file mode 100644
index 0000000..66d70cd
--- /dev/null
+++ b/javac-plugin/src/test/java/org/moditect/deptective/internal/graph/DependencyStructureMatrixTest.java
@@ -0,0 +1,53 @@
+/**
+ * Copyright 2019 The ModiTect authors
+ *
+ * Licensed 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.moditect.deptective.internal.graph;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.List;
+
+import org.junit.Test;
+
+/**
+ *
+ * @author Gerd Wütherich (gw@code-kontor.io)
+ */
+public class DependencyStructureMatrixTest {
+
+ @Test
+ public void detectCycle() {
+
+ //
+ List nodes = TestModelCreator.createDummyModel();
+
+ //
+ IDependencyStructureMatrix dsm = GraphUtils.createDependencyStructureMatrix(nodes);
+
+ // assert ordered nodes
+ assertThat(dsm.getOrderedNodes()).hasSize(4).containsExactly(
+ nodes.get(0), nodes.get(1), nodes.get(2),
+ nodes.get(3)
+ );
+
+ // assert upward dependencies
+ assertThat(dsm.getUpwardDependencies()).hasSize(1)
+ .containsExactly(nodes.get(3).getOutgoingDependencyTo(nodes.get(2)));
+
+ // assert cycles
+ assertThat(dsm.getCycles()).hasSize(1);
+ assertThat(dsm.getCycles().get(0)).containsExactly(nodes.get(3), nodes.get(2));
+ }
+}
diff --git a/javac-plugin/src/test/java/org/moditect/deptective/internal/graph/FasNodeSorterTest.java b/javac-plugin/src/test/java/org/moditect/deptective/internal/graph/FasNodeSorterTest.java
new file mode 100644
index 0000000..1971754
--- /dev/null
+++ b/javac-plugin/src/test/java/org/moditect/deptective/internal/graph/FasNodeSorterTest.java
@@ -0,0 +1,42 @@
+/**
+ * Copyright 2019 The ModiTect authors
+ *
+ * Licensed 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.moditect.deptective.internal.graph;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.List;
+
+import org.junit.Test;
+import org.moditect.deptective.internal.graph.INodeSorter.SortResult;
+
+public class FasNodeSorterTest {
+
+ @Test
+ public void sortNodes() {
+
+ //
+ List nodes = TestModelCreator.createDummyModel();
+
+ //
+ INodeSorter nodeSorter = GraphUtils.createFasNodeSorter();
+
+ //
+ SortResult sortResult = nodeSorter.sort(nodes);
+
+ //
+ assertThat(sortResult.getUpwardsDependencies()).hasSize(1);
+ }
+}
diff --git a/javac-plugin/src/test/java/org/moditect/deptective/internal/graph/StronglyConnectedComponentsTest.java b/javac-plugin/src/test/java/org/moditect/deptective/internal/graph/StronglyConnectedComponentsTest.java
new file mode 100644
index 0000000..21d2fbf
--- /dev/null
+++ b/javac-plugin/src/test/java/org/moditect/deptective/internal/graph/StronglyConnectedComponentsTest.java
@@ -0,0 +1,49 @@
+/**
+ * Copyright 2019 The ModiTect authors
+ *
+ * Licensed 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.moditect.deptective.internal.graph;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.List;
+
+import org.junit.Test;
+
+/**
+ *
+ * @author Gerd Wütherich (gw@code-kontor.io)
+ */
+public class StronglyConnectedComponentsTest {
+
+ @Test
+ public void detectCycle() {
+
+ //
+ List nodes = TestModelCreator.createDummyModel();
+
+ //
+ List> stronglyConnectedComponents = GraphUtils.detectStronglyConnectedComponents(nodes);
+
+ //
+ assertThat(stronglyConnectedComponents).hasSize(3);
+
+ //
+ for (List scc : stronglyConnectedComponents) {
+ if (scc.size() == 2) {
+ assertThat(scc).contains(nodes.get(2)).contains(nodes.get(3));
+ }
+ }
+ }
+}
diff --git a/javac-plugin/src/test/java/org/moditect/deptective/internal/graph/TestModelCreator.java b/javac-plugin/src/test/java/org/moditect/deptective/internal/graph/TestModelCreator.java
new file mode 100644
index 0000000..87dde64
--- /dev/null
+++ b/javac-plugin/src/test/java/org/moditect/deptective/internal/graph/TestModelCreator.java
@@ -0,0 +1,49 @@
+/**
+ * Copyright 2019 The ModiTect authors
+ *
+ * Licensed 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.moditect.deptective.internal.graph;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.moditect.deptective.internal.graph.Dependency;
+import org.moditect.deptective.internal.graph.Node;
+
+/**
+ *
+ * @author Gerd Wütherich (gw@code-kontor.io)
+ */
+public class TestModelCreator {
+
+ /**
+ *
+ * @return
+ */
+ public static List createDummyModel() {
+
+ Node p1 = new Node("p1");
+ Node p2 = new Node("p2");
+ Node p3 = new Node("p3");
+ Node p4 = new Node("p4");
+
+ new Dependency(p1, p2, 13);
+ new Dependency(p2, p3, 57);
+ new Dependency(p3, p4, 45);
+ new Dependency(p4, p3, 3);
+
+ return new ArrayList<>(Arrays.asList(p1, p2, p3, p4));
+ }
+}
diff --git a/pom.xml b/pom.xml
index 9b24588..29fb116 100644
--- a/pom.xml
+++ b/pom.xml
@@ -66,6 +66,11 @@
jackson-databind
2.9.8
+
+ com.google.guava
+ guava
+ 27.0.1-jre
+