From f8fdf48f229b01eee6eebc84d20c89f7f480dc05 Mon Sep 17 00:00:00 2001 From: nik Date: Tue, 7 Jun 2011 11:36:31 +0400 Subject: [PATCH] algorithms on graphs rearranged: implemented efficient algorithm to find k shortest paths between two vertices, Dijkstra algorithm replaced by BFS --- .../CyclicDependenciesBuilder.java | 3 +- .../cyclicDependencies/ShortestPathUtil.java | 198 ----------- .../ModulesDependenciesPanel.java | 22 +- .../dependencies/DijkstraAlgorithmTest.java | 147 -------- .../dependencies/SearchCyclesTest.java | 177 ---------- .../intellij/util/graph/GraphAlgorithms.java | 49 +++ .../util/graph/impl/CycleFinder.java} | 38 +- .../util/graph/impl/GraphAlgorithmsImpl.java | 81 +++++ .../intellij/util/graph/impl/GraphEdge.java | 59 ++++ .../util/graph/impl/KShortestPathsFinder.java | 331 ++++++++++++++++++ .../util/graph/impl/ShortestPathFinder.java | 70 ++++ .../intellij/util/graph/GraphTestCase.java | 58 +++ .../util/graph/KShortestPathsFinderTest.java | 168 +++++++++ .../intellij/util/graph/SearchCyclesTest.java | 96 +++++ .../intellij/util/graph/ShortestPathTest.java | 96 +++++ .../src/META-INF/PlatformExtensions.xml | 3 + 16 files changed, 1038 insertions(+), 558 deletions(-) delete mode 100644 platform/lang-impl/src/com/intellij/cyclicDependencies/ShortestPathUtil.java delete mode 100644 platform/lang-impl/testSrc/com/intellij/dependencies/DijkstraAlgorithmTest.java delete mode 100644 platform/lang-impl/testSrc/com/intellij/dependencies/SearchCyclesTest.java create mode 100644 platform/platform-impl/src/com/intellij/util/graph/GraphAlgorithms.java rename platform/{lang-impl/src/com/intellij/cyclicDependencies/CyclicGraphUtil.java => platform-impl/src/com/intellij/util/graph/impl/CycleFinder.java} (71%) create mode 100644 platform/platform-impl/src/com/intellij/util/graph/impl/GraphAlgorithmsImpl.java create mode 100644 platform/platform-impl/src/com/intellij/util/graph/impl/GraphEdge.java create mode 100644 platform/platform-impl/src/com/intellij/util/graph/impl/KShortestPathsFinder.java create mode 100644 platform/platform-impl/src/com/intellij/util/graph/impl/ShortestPathFinder.java create mode 100644 platform/platform-impl/testSrc/com/intellij/util/graph/GraphTestCase.java create mode 100644 platform/platform-impl/testSrc/com/intellij/util/graph/KShortestPathsFinderTest.java create mode 100644 platform/platform-impl/testSrc/com/intellij/util/graph/SearchCyclesTest.java create mode 100644 platform/platform-impl/testSrc/com/intellij/util/graph/ShortestPathTest.java diff --git a/java/java-impl/src/com/intellij/cyclicDependencies/CyclicDependenciesBuilder.java b/java/java-impl/src/com/intellij/cyclicDependencies/CyclicDependenciesBuilder.java index 10f55f2dfadd..a38a1485c606 100644 --- a/java/java-impl/src/com/intellij/cyclicDependencies/CyclicDependenciesBuilder.java +++ b/java/java-impl/src/com/intellij/cyclicDependencies/CyclicDependenciesBuilder.java @@ -28,6 +28,7 @@ import com.intellij.packageDependencies.ForwardDependenciesBuilder; import com.intellij.psi.*; import com.intellij.util.graph.CachingSemiGraph; import com.intellij.util.graph.Graph; +import com.intellij.util.graph.GraphAlgorithms; import com.intellij.util.graph.GraphGenerator; import java.util.*; @@ -217,7 +218,7 @@ public class CyclicDependenciesBuilder{ paths2Pack = new HashSet>(); result.put(psiPackage, paths2Pack); } - paths2Pack.addAll(CyclicGraphUtil.getNodeCycles(myGraph, psiPackage)); + paths2Pack.addAll(GraphAlgorithms.getInstance().findCycles(myGraph, psiPackage)); } return result; } diff --git a/platform/lang-impl/src/com/intellij/cyclicDependencies/ShortestPathUtil.java b/platform/lang-impl/src/com/intellij/cyclicDependencies/ShortestPathUtil.java deleted file mode 100644 index 338d2c4df89a..000000000000 --- a/platform/lang-impl/src/com/intellij/cyclicDependencies/ShortestPathUtil.java +++ /dev/null @@ -1,198 +0,0 @@ -/* - * Copyright 2000-2009 JetBrains s.r.o. - * - * 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 com.intellij.cyclicDependencies; - -import com.intellij.util.graph.Graph; -import com.intellij.util.ui.tree.TreeUtil; - -import javax.swing.tree.DefaultMutableTreeNode; -import javax.swing.tree.DefaultTreeModel; -import javax.swing.tree.TreeModel; -import javax.swing.tree.TreeNode; -import java.util.*; - -/** - * User: anna - * Date: Feb 11, 2005 - */ -//DijkstraAlgorithm -public class ShortestPathUtil { - private final HashSet myVisited; - private final Set myProcessingNodes; - - private DefaultTreeModel myShortestPathTree; - - private final Graph myGraph; - - public ShortestPathUtil(Graph graph) { - myVisited = new HashSet(); - myProcessingNodes = new HashSet(); - myGraph = graph; - } - - public List getShortestPath(Node from, Node to) { - ArrayList result = new ArrayList(); - if (myShortestPathTree == null || ((DefaultMutableTreeNode)myShortestPathTree.getRoot()).getUserObject() != from) { - shortestPath(from); - } - final boolean flag = traverse(to, result); - return flag ? result : null; - } - - private boolean traverse(Node to, List path){ - DefaultMutableTreeNode treeNode = findNodeByUserObject(to); - if (treeNode == null){ - return false; - } - while (treeNode != null){ - path.add((Node)treeNode.getUserObject()); - treeNode = (DefaultMutableTreeNode)treeNode.getParent(); - } - return true; - } - - - private TreeModel shortestPath(Node from) { - myShortestPathTree = new DefaultTreeModel(new DefaultMutableTreeNode(from)); - - myProcessingNodes.add(new ProcessingNode(null, from, 0)); - - while (!myProcessingNodes.isEmpty()) { - moveToVisited(); - } - - myVisited.clear(); - myProcessingNodes.clear(); - - return myShortestPathTree; - } - - private DefaultMutableTreeNode findNodeByUserObject(final Node nodeToFind){ - final ArrayList parent = new ArrayList(); - TreeUtil.traverseDepth((TreeNode)myShortestPathTree.getRoot(), new TreeUtil.Traverse() { - public boolean accept(Object node) { - final DefaultMutableTreeNode treeNode = ((DefaultMutableTreeNode)node); - if (treeNode.getUserObject() != null && treeNode.getUserObject().equals(nodeToFind)) { - parent.add(treeNode); - return false; - } - return true; - } - }); - return parent.size() > 0 ? parent.get(0) : null; - } - - private void moveToVisited() { - ProcessingNode priorityNode = null; - for (Iterator iterator = myProcessingNodes.iterator(); iterator.hasNext();) { - ProcessingNode processingNode = iterator.next(); - if (priorityNode != null) { - if (priorityNode.getPriority() > processingNode.getPriority()) { - priorityNode = processingNode; - } - } - else { - priorityNode = processingNode; - } - } - myProcessingNodes.remove(priorityNode); - myVisited.add(priorityNode.myToNode); - if (priorityNode.myFromNode != null) { - final DefaultMutableTreeNode parentNode = findNodeByUserObject(priorityNode.myFromNode); - if (parentNode != null){ - myShortestPathTree.insertNodeInto(new DefaultMutableTreeNode(priorityNode.myToNode), parentNode, 0); - } - } - moveAdjacentVerticesToProcessing(priorityNode); - } - - private void moveAdjacentVerticesToProcessing(ProcessingNode priorityNode) { - Node fromNode = priorityNode.getToNode(); - int priority = priorityNode.getPriority(); - Iterator iterator = myGraph.getIn(fromNode); - Node toNode; - while (iterator.hasNext()) { - toNode = iterator.next(); - if (myVisited.contains(toNode)){ - continue; - } - final ProcessingNode processingNode = getProcessingNodeByFromNode(toNode); - if (processingNode == null) { - myProcessingNodes.add(new ProcessingNode(fromNode, toNode, priority + 1)); - } - else { - if (processingNode.getPriority() > priority + 1) { - processingNode.setPriority(priority + 1); - processingNode.setFromNode(fromNode); - } - } - } - } - - private ProcessingNode getProcessingNodeByFromNode(Node fromNode) { - for (Iterator iterator = myProcessingNodes.iterator(); iterator.hasNext();) { - ProcessingNode processingNode = iterator.next(); - if (processingNode.getFromNode() == fromNode) { - return processingNode; - } - } - return null; - } - - - - private class ProcessingNode { - private Node myFromNode; - private Node myToNode; - private int myPriority; - - public ProcessingNode(final Node fromNode, final Node toNode, final int priority) { - myFromNode = fromNode; - myToNode = toNode; - myPriority = priority; - } - - public Node getFromNode() { - return myFromNode; - } - - public Node getToNode() { - return myToNode; - } - - public int getPriority() { - return myPriority; - } - - public void setPriority(final int priority) { - myPriority = priority; - } - - public void setFromNode(final Node fromNode) { - myFromNode = fromNode; - } - - public void setToNode(final Node toNode) { - myToNode = toNode; - } - } - -} - - - - diff --git a/platform/lang-impl/src/com/intellij/moduleDependencies/ModulesDependenciesPanel.java b/platform/lang-impl/src/com/intellij/moduleDependencies/ModulesDependenciesPanel.java index 6ff8ca6679b6..53564cc8c009 100644 --- a/platform/lang-impl/src/com/intellij/moduleDependencies/ModulesDependenciesPanel.java +++ b/platform/lang-impl/src/com/intellij/moduleDependencies/ModulesDependenciesPanel.java @@ -19,7 +19,6 @@ package com.intellij.moduleDependencies; import com.intellij.CommonBundle; import com.intellij.ProjectTopics; import com.intellij.analysis.AnalysisScopeBundle; -import com.intellij.cyclicDependencies.CyclicGraphUtil; import com.intellij.ide.CommonActionsManager; import com.intellij.ide.TreeExpander; import com.intellij.ide.actions.ContextHelpAction; @@ -37,7 +36,6 @@ import com.intellij.openapi.roots.ui.configuration.ProjectSettingsService; import com.intellij.openapi.ui.Splitter; import com.intellij.openapi.util.IconLoader; import com.intellij.openapi.util.text.StringUtil; -import com.intellij.pom.Navigatable; import com.intellij.pom.NavigatableWithText; import com.intellij.ui.*; import com.intellij.ui.content.Content; @@ -47,6 +45,7 @@ import com.intellij.util.containers.Convertor; import com.intellij.util.containers.HashMap; import com.intellij.util.graph.DFSTBuilder; import com.intellij.util.graph.Graph; +import com.intellij.util.graph.GraphAlgorithms; import com.intellij.util.ui.UIUtil; import com.intellij.util.ui.tree.TreeUtil; import org.jetbrains.annotations.NonNls; @@ -191,7 +190,7 @@ public class ModulesDependenciesPanel extends JPanel implements ModuleRootListen private void buildRightTree(Module module){ final DefaultMutableTreeNode root = (DefaultMutableTreeNode)myRightTreeModel.getRoot(); root.removeAllChildren(); - final Set> cycles = CyclicGraphUtil.getNodeCycles(myModulesGraph, module); + final Set> cycles = GraphAlgorithms.getInstance().findCycles(myModulesGraph, module); int index = 1; for (List modules : cycles) { final DefaultMutableTreeNode cycle = new DefaultMutableTreeNode( @@ -223,7 +222,7 @@ public class ModulesDependenciesPanel extends JPanel implements ModuleRootListen if (!module.isDisposed()) { Boolean isInCycle = inCycle.get(module); if (isInCycle == null) { - isInCycle = !CyclicGraphUtil.getNodeCycles(myModulesGraph, module).isEmpty(); + isInCycle = !GraphAlgorithms.getInstance().findCycles(myModulesGraph, module).isEmpty(); inCycle.put(module, isInCycle); } final DefaultMutableTreeNode moduleNode = new DefaultMutableTreeNode(new MyUserObject(isInCycle.booleanValue(), module)); @@ -407,20 +406,7 @@ public class ModulesDependenciesPanel extends JPanel implements ModuleRootListen return graph; } else { - return new Graph() { - public Collection getNodes() { - return graph.getNodes(); - } - - public Iterator getIn(final Module n) { - return graph.getOut(n); - } - - public Iterator getOut(final Module n) { - return graph.getIn(n); - } - - }; + return GraphAlgorithms.getInstance().invertEdgeDirections(graph); } } diff --git a/platform/lang-impl/testSrc/com/intellij/dependencies/DijkstraAlgorithmTest.java b/platform/lang-impl/testSrc/com/intellij/dependencies/DijkstraAlgorithmTest.java deleted file mode 100644 index 470b790971ca..000000000000 --- a/platform/lang-impl/testSrc/com/intellij/dependencies/DijkstraAlgorithmTest.java +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Copyright 2000-2009 JetBrains s.r.o. - * - * 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 com.intellij.dependencies; - -import com.intellij.cyclicDependencies.ShortestPathUtil; -import com.intellij.util.ArrayUtil; -import com.intellij.util.graph.Graph; -import com.intellij.util.graph.GraphGenerator; -import junit.framework.TestCase; - -import java.util.*; - -/** - * User: anna - * Date: Feb 11, 2005 - */ -public class DijkstraAlgorithmTest extends TestCase{ - private Graph myGraph; - public void test1() throws Exception{ - final HashMap graph = new HashMap(); - graph.put("a", new String[]{"b","c", "d"}); - graph.put("b", new String[]{"d", "c"}); - graph.put("c", new String[]{"b"}); - graph.put("d", ArrayUtil.EMPTY_STRING_ARRAY); - myGraph = new GraphGenerator(new GraphGenerator.SemiGraph() { - @Override - public Collection getNodes() { - return graph.keySet(); - } - - @Override - public Iterator getIn(final String n) { - String[] in = graph.get(n); - if (in == null){ - in = ArrayUtil.EMPTY_STRING_ARRAY; - } - return Arrays.asList(in).iterator(); - } - }); - final ShortestPathUtil algorithm = new ShortestPathUtil(myGraph); - final List shortestPath = algorithm.getShortestPath("a", "b"); - checkResult(new String[]{"b","a"}, shortestPath); - } - - public void test2() throws Exception{ - final HashMap graph = new HashMap(); - graph.put("a", new String[]{"b", "d"}); - graph.put("b", new String[]{"d"}); - graph.put("c", new String[]{"a"}); - graph.put("d", new String[]{"a", "c"}); - myGraph = new GraphGenerator(new GraphGenerator.SemiGraph() { - @Override - public Collection getNodes() { - return graph.keySet(); - } - - @Override - public Iterator getIn(final String n) { - String[] in = graph.get(n); - if (in == null){ - in = ArrayUtil.EMPTY_STRING_ARRAY; - } - return Arrays.asList(in).iterator(); - } - }); - final ShortestPathUtil algorithm = new ShortestPathUtil(myGraph); - final List shortestPath = algorithm.getShortestPath("b", "c"); - checkResult(new String[]{"c","d","b"}, shortestPath); - } - - public void test3() throws Exception{ - final HashMap graph = new HashMap(); - graph.put("a", new String[]{"c", "d"}); - graph.put("b", new String[]{"a"}); - graph.put("c", new String[]{"d"}); - graph.put("d", new String[]{"a", "b"}); - myGraph = new GraphGenerator(new GraphGenerator.SemiGraph() { - @Override - public Collection getNodes() { - return graph.keySet(); - } - - @Override - public Iterator getIn(final String n) { - String[] in = graph.get(n); - if (in == null){ - in = ArrayUtil.EMPTY_STRING_ARRAY; - } - return Arrays.asList(in).iterator(); - } - }); - final ShortestPathUtil algorithm = new ShortestPathUtil(myGraph); - final List shortestPath = algorithm.getShortestPath("c", "b"); - checkResult(new String[]{"b","d","c"}, shortestPath); - } - - public void test4() throws Exception{ - final HashMap graph = new HashMap(); - graph.put("a", new String[]{"c"}); - graph.put("b", new String[]{"a"}); - graph.put("c", new String[]{"e"}); - graph.put("d", new String[]{"b"}); - graph.put("e", new String[]{"d", "f", "a"}); - graph.put("f", new String[]{"d"}); - myGraph = new GraphGenerator(new GraphGenerator.SemiGraph() { - @Override - public Collection getNodes() { - return graph.keySet(); - } - - @Override - public Iterator getIn(final String n) { - String[] in = graph.get(n); - if (in == null){ - in = ArrayUtil.EMPTY_STRING_ARRAY; - } - return Arrays.asList(in).iterator(); - } - }); - final ShortestPathUtil algorithm = new ShortestPathUtil(myGraph); - final List shortestPath = algorithm.getShortestPath("c", "b"); - checkResult(new String[]{"b","d","e","c"}, shortestPath); - } - - private static void checkResult(final String [] expectedPath, List path){ - assertNotNull(path); - assertEquals(expectedPath.length, path.size()); - int index = 0; - for (String s : path) { - assertEquals(expectedPath[index++], s); - } - } -} diff --git a/platform/lang-impl/testSrc/com/intellij/dependencies/SearchCyclesTest.java b/platform/lang-impl/testSrc/com/intellij/dependencies/SearchCyclesTest.java deleted file mode 100644 index 0c795b169099..000000000000 --- a/platform/lang-impl/testSrc/com/intellij/dependencies/SearchCyclesTest.java +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Copyright 2000-2009 JetBrains s.r.o. - * - * 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 com.intellij.dependencies; - -import com.intellij.cyclicDependencies.CyclicGraphUtil; -import com.intellij.util.ArrayUtil; -import com.intellij.util.graph.Graph; -import com.intellij.util.graph.GraphGenerator; -import junit.framework.TestCase; - -import java.util.*; - -/** - * User: anna - * Date: Feb 13, 2005 - */ -public class SearchCyclesTest extends TestCase{ - public void test1() throws Exception{ - final HashMap graph = new HashMap(); - graph.put("a", new String[]{"c", "d"}); - graph.put("b", new String[]{"a"}); - graph.put("c", new String[]{"d"}); - graph.put("d", new String[]{"a", "b"}); - Graph graphToInvestigate = new GraphGenerator(new GraphGenerator.SemiGraph() { - @Override - public Collection getNodes() { - return graph.keySet(); - } - - @Override - public Iterator getIn(final String n) { - String[] in = graph.get(n); - if (in == null){ - in = ArrayUtil.EMPTY_STRING_ARRAY; - } - return Arrays.asList(in).iterator(); - } - }); - final Set> nodeCycles = CyclicGraphUtil.getNodeCycles(graphToInvestigate, "a"); - checkResult(new String[][]{{"d","a"},{"b","d","c","a"}}, nodeCycles); - } - - public void test2() throws Exception{ - final HashMap graph = new HashMap(); - graph.put("a", new String[]{"c"}); - graph.put("b", new String[]{"a"}); - graph.put("c", new String[]{"d"}); - graph.put("d", new String[]{"b","e"}); - graph.put("e", new String[]{"d"}); - Graph graphToInvestigate = new GraphGenerator(new GraphGenerator.SemiGraph() { - @Override - public Collection getNodes() { - return graph.keySet(); - } - - @Override - public Iterator getIn(final String n) { - String[] in = graph.get(n); - if (in == null){ - in = ArrayUtil.EMPTY_STRING_ARRAY; - } - return Arrays.asList(in).iterator(); - } - }); - final Set> nodeCycles = CyclicGraphUtil.getNodeCycles(graphToInvestigate, "a"); - checkResult(new String[][]{{"b","d","c","a"}}, nodeCycles); - } - - public void test3() throws Exception{ - final HashMap graph = new HashMap(); - graph.put("a", new String[]{"d"}); - graph.put("b", new String[]{"a"}); - graph.put("d", new String[]{"a", "b"}); - Graph graphToInvestigate = new GraphGenerator(new GraphGenerator.SemiGraph() { - @Override - public Collection getNodes() { - return graph.keySet(); - } - - @Override - public Iterator getIn(final String n) { - String[] in = graph.get(n); - if (in == null){ - in = ArrayUtil.EMPTY_STRING_ARRAY; - } - return Arrays.asList(in).iterator(); - } - }); - final Set> nodeCycles = CyclicGraphUtil.getNodeCycles(graphToInvestigate, "a"); - checkResult(new String[][]{{"d","a"}}, nodeCycles); - } - - public void test4() throws Exception{ - final HashMap graph = new HashMap(); - graph.put("a", new String[]{"c"}); - graph.put("b", new String[]{"a"}); - graph.put("c", new String[]{"e"}); - graph.put("d", new String[]{"b"}); - graph.put("e", new String[]{"d", "f"}); - graph.put("f", new String[]{"d"}); - Graph graphToInvestigate = new GraphGenerator(new GraphGenerator.SemiGraph() { - @Override - public Collection getNodes() { - return graph.keySet(); - } - - @Override - public Iterator getIn(final String n) { - String[] in = graph.get(n); - if (in == null){ - in = ArrayUtil.EMPTY_STRING_ARRAY; - } - return Arrays.asList(in).iterator(); - } - }); - final Set> nodeCycles = CyclicGraphUtil.getNodeCycles(graphToInvestigate, "a"); - checkResult(new String[][]{{"b","d","e","c","a"}}, nodeCycles); - } - - public void test5() throws Exception{ - final HashMap graph = new HashMap(); - graph.put("a", new String[]{"c"}); - graph.put("b", new String[]{"a"}); - graph.put("c", new String[]{"e"}); - graph.put("d", new String[]{"b"}); - graph.put("e", new String[]{"d", "f", "a"}); - graph.put("f", new String[]{"d"}); - Graph graphToInvestigate = new GraphGenerator(new GraphGenerator.SemiGraph() { - @Override - public Collection getNodes() { - return graph.keySet(); - } - - @Override - public Iterator getIn(final String n) { - String[] in = graph.get(n); - if (in == null){ - in = ArrayUtil.EMPTY_STRING_ARRAY; - } - return Arrays.asList(in).iterator(); - } - }); - final Set> nodeCycles = CyclicGraphUtil.getNodeCycles(graphToInvestigate, "a"); - checkResult(new String[][]{{"b","d","e","c","a"}, {"e","c","a"}}, nodeCycles); - } - - private static void checkResult(String[][] expected, Set> cycles){ - assertEquals(expected.length, cycles.size()); - for (List strings : cycles) { - assertTrue(findInMatrix(expected, ArrayUtil.toStringArray(strings)) > -1); - } - } - - private static int findInMatrix(String [][] matrix, String [] string){ - for (int i = 0; i < matrix.length; i++) { - String[] strings = matrix[i]; - if (Arrays.equals(strings, string)){ - return i; - } - } - return -1; - } -} diff --git a/platform/platform-impl/src/com/intellij/util/graph/GraphAlgorithms.java b/platform/platform-impl/src/com/intellij/util/graph/GraphAlgorithms.java new file mode 100644 index 000000000000..90cd66bf0049 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/util/graph/GraphAlgorithms.java @@ -0,0 +1,49 @@ +/* + * Copyright 2000-2011 JetBrains s.r.o. + * + * 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 com.intellij.util.graph; + +import com.intellij.openapi.components.ServiceManager; +import com.intellij.openapi.progress.ProgressIndicator; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.Set; + +/** + * @author nik + */ +public abstract class GraphAlgorithms { + public static GraphAlgorithms getInstance() { + return ServiceManager.getService(GraphAlgorithms.class); + } + + @Nullable + public abstract List findShortestPath(@NotNull Graph graph, @NotNull Node start, @NotNull Node finish); + + @NotNull + public abstract List> findKShortestPaths(@NotNull Graph graph, @NotNull Node start, @NotNull Node finish, int k, + @NotNull ProgressIndicator progressIndicator); + + @NotNull + public abstract Set> findCycles(@NotNull Graph graph, @NotNull Node node); + + @NotNull + public abstract List> removePathsWithCycles(@NotNull List> paths); + + @NotNull + public abstract Graph invertEdgeDirections(@NotNull Graph graph); +} diff --git a/platform/lang-impl/src/com/intellij/cyclicDependencies/CyclicGraphUtil.java b/platform/platform-impl/src/com/intellij/util/graph/impl/CycleFinder.java similarity index 71% rename from platform/lang-impl/src/com/intellij/cyclicDependencies/CyclicGraphUtil.java rename to platform/platform-impl/src/com/intellij/util/graph/impl/CycleFinder.java index eb97e63f984f..fa8695c5c8bf 100644 --- a/platform/lang-impl/src/com/intellij/cyclicDependencies/CyclicGraphUtil.java +++ b/platform/platform-impl/src/com/intellij/util/graph/impl/CycleFinder.java @@ -14,9 +14,10 @@ * limitations under the License. */ -package com.intellij.cyclicDependencies; +package com.intellij.util.graph.impl; import com.intellij.util.graph.Graph; +import org.jetbrains.annotations.NotNull; import java.util.*; @@ -24,25 +25,29 @@ import java.util.*; * User: anna * Date: Feb 13, 2005 */ -public class CyclicGraphUtil { - private CyclicGraphUtil() { +public class CycleFinder { + private final Graph myGraph; + + public CycleFinder(Graph graph) { + myGraph = graph; } - public static Set> getNodeCycles(final Graph graph, final Node node){ - Set> result = new HashSet>(); + @NotNull + public Set> getNodeCycles(final Node node){ + final Set> result = new HashSet>(); final Graph graphWithoutNode = new Graph() { public Collection getNodes() { - final Collection nodes = graph.getNodes(); + final Collection nodes = myGraph.getNodes(); nodes.remove(node); return nodes; } public Iterator getIn(final Node n) { final HashSet nodes = new HashSet(); - final Iterator in = graph.getIn(n); - for(;in.hasNext();){ + final Iterator in = myGraph.getIn(n); + while (in.hasNext()) { nodes.add(in.next()); } nodes.remove(node); @@ -51,8 +56,8 @@ public class CyclicGraphUtil { public Iterator getOut(final Node n) { final HashSet nodes = new HashSet(); - final Iterator out = graph.getOut(n); - for(;out.hasNext();){ + final Iterator out = myGraph.getOut(n); + while (out.hasNext()) { nodes.add(out.next()); } nodes.remove(node); @@ -62,13 +67,13 @@ public class CyclicGraphUtil { }; final HashSet inNodes = new HashSet(); - final Iterator in = graph.getIn(node); - for(;in.hasNext();){ + final Iterator in = myGraph.getIn(node); + while (in.hasNext()) { inNodes.add(in.next()); } final HashSet outNodes = new HashSet(); - final Iterator out = graph.getOut(node); - for(;out.hasNext();){ + final Iterator out = myGraph.getOut(node); + while (out.hasNext()) { outNodes.add(out.next()); } @@ -84,11 +89,10 @@ public class CyclicGraphUtil { inNodes.removeAll(retainNodes); outNodes.removeAll(retainNodes); - final ShortestPathUtil algorithm = new ShortestPathUtil(graphWithoutNode); - + ShortestPathFinder finder = new ShortestPathFinder(graphWithoutNode); for (Node fromNode : outNodes) { for (Node toNode : inNodes) { - final List shortestPath = algorithm.getShortestPath(toNode, fromNode); + final List shortestPath = finder.findPath(fromNode, toNode); if (shortestPath != null) { ArrayList path = new ArrayList(); path.addAll(shortestPath); diff --git a/platform/platform-impl/src/com/intellij/util/graph/impl/GraphAlgorithmsImpl.java b/platform/platform-impl/src/com/intellij/util/graph/impl/GraphAlgorithmsImpl.java new file mode 100644 index 000000000000..a8a45f15bf4a --- /dev/null +++ b/platform/platform-impl/src/com/intellij/util/graph/impl/GraphAlgorithmsImpl.java @@ -0,0 +1,81 @@ +/* + * Copyright 2000-2011 JetBrains s.r.o. + * + * 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 com.intellij.util.graph.impl; + +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.util.graph.Graph; +import com.intellij.util.graph.GraphAlgorithms; +import org.jetbrains.annotations.NotNull; + +import java.util.*; + +/** + * @author nik + */ +public class GraphAlgorithmsImpl extends GraphAlgorithms { + @Override + public List findShortestPath(@NotNull Graph graph, @NotNull Node start, @NotNull Node finish) { + return new ShortestPathFinder(graph).findPath(start, finish); + } + + @NotNull + @Override + public List> findKShortestPaths(@NotNull Graph graph, @NotNull Node start, @NotNull Node finish, int k, + @NotNull ProgressIndicator progressIndicator) { + return new KShortestPathsFinder(graph, start, finish, progressIndicator).findShortestPaths(k); + } + + @NotNull + @Override + public Set> findCycles(@NotNull Graph graph, @NotNull Node node) { + return new CycleFinder(graph).getNodeCycles(node); + } + + @NotNull + @Override + public Graph invertEdgeDirections(@NotNull final Graph graph) { + return new Graph() { + public Collection getNodes() { + return graph.getNodes(); + } + + public Iterator getIn(final Node n) { + return graph.getOut(n); + } + + public Iterator getOut(final Node n) { + return graph.getIn(n); + } + + }; + } + + @NotNull + @Override + public List> removePathsWithCycles(@NotNull List> paths) { + final List> result = new ArrayList>(); + for (List path : paths) { + if (!containsCycle(path)) { + result.add(path); + } + } + return result; + } + + private static boolean containsCycle(List path) { + return new HashSet(path).size() != path.size(); + } +} diff --git a/platform/platform-impl/src/com/intellij/util/graph/impl/GraphEdge.java b/platform/platform-impl/src/com/intellij/util/graph/impl/GraphEdge.java new file mode 100644 index 000000000000..d7f977f0a810 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/util/graph/impl/GraphEdge.java @@ -0,0 +1,59 @@ +/* + * Copyright 2000-2011 JetBrains s.r.o. + * + * 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 com.intellij.util.graph.impl; + +import org.jetbrains.annotations.NotNull; + +/** + * @author nik + */ +class GraphEdge { + private final Node myStart; + private final Node myFinish; + private final int myDelta; + + GraphEdge(@NotNull Node start, @NotNull Node finish, int delta) { + myStart = start; + myFinish = finish; + myDelta = delta; + } + + public Node getStart() { + return myStart; + } + + public Node getFinish() { + return myFinish; + } + + public int getDelta() { + return myDelta; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + GraphEdge edge = (GraphEdge)o; + return myFinish.equals(edge.myFinish) && myStart.equals(edge.myStart); + } + + @Override + public int hashCode() { + return 31 * myStart.hashCode() + myFinish.hashCode(); + } +} diff --git a/platform/platform-impl/src/com/intellij/util/graph/impl/KShortestPathsFinder.java b/platform/platform-impl/src/com/intellij/util/graph/impl/KShortestPathsFinder.java new file mode 100644 index 000000000000..7e6e3d35a001 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/util/graph/impl/KShortestPathsFinder.java @@ -0,0 +1,331 @@ +/* + * Copyright 2000-2011 JetBrains s.r.o. + * + * 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 com.intellij.util.graph.impl; + +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.progress.ProcessCanceledException; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.util.containers.FList; +import com.intellij.util.containers.MultiMap; +import com.intellij.util.graph.Graph; +import gnu.trove.TObjectIntHashMap; +import org.jetbrains.annotations.NotNull; + +import java.util.*; + +/** + * Algorithm to search k shortest paths between to vertices in unweighted directed graph. + * Based on article "Finding the k shortest paths" by D. Eppstein, 1997. + * + * @author nik + */ +public class KShortestPathsFinder { + private static final Logger LOG = Logger.getInstance("#com.intellij.util.graph.impl.KShortestPathsFinder"); + private final Graph myGraph; + private final Node myStart; + private final Node myFinish; + private final ProgressIndicator myProgressIndicator; + private MultiMap> myNonTreeEdges; + private List mySortedNodes; + private Map myNextNodes; + private Map> myOutRoots; + private Map> myHeaps; + + public KShortestPathsFinder(@NotNull Graph graph, @NotNull Node start, @NotNull Node finish, @NotNull ProgressIndicator progressIndicator) { + myGraph = graph; + myStart = start; + myFinish = finish; + myProgressIndicator = progressIndicator; + } + + private void computeDistancesToTarget() { + myNonTreeEdges = new MultiMap>(); + mySortedNodes = new ArrayList(); + myNextNodes = new HashMap(); + TObjectIntHashMap distances = new TObjectIntHashMap(); + Deque nodes = new ArrayDeque(); + nodes.addLast(myFinish); + distances.put(myFinish, 0); + while (!nodes.isEmpty()) { + myProgressIndicator.checkCanceled(); + Node node = nodes.removeFirst(); + mySortedNodes.add(node); + int d = distances.get(node) + 1; + Iterator iterator = myGraph.getIn(node); + while (iterator.hasNext()) { + Node prev = iterator.next(); + if (distances.containsKey(prev)) { + int dPrev = distances.get(prev); + myNonTreeEdges.putValue(prev, new GraphEdge(prev, node, d - dPrev)); + continue; + } + distances.put(prev, d); + myNextNodes.put(prev, node); + nodes.addLast(prev); + } + } + } + + private void buildOutHeaps() { + myOutRoots = new HashMap>(); + for (Node node : mySortedNodes) { + myProgressIndicator.checkCanceled(); + List> heapNodes = new ArrayList>(); + Collection> edges = myNonTreeEdges.get(node); + if (edges.isEmpty()) continue; + + HeapNode root = null; + for (GraphEdge edge : edges) { + HeapNode heapNode = new HeapNode(edge); + heapNodes.add(heapNode); + if (root == null || root.myEdge.getDelta() > heapNode.myEdge.getDelta()) { + root = heapNode; + } + } + LOG.assertTrue(root != null); + heapNodes.remove(root); + myOutRoots.put(node, root); + if (!heapNodes.isEmpty()) { + for (int j = 1; j < heapNodes.size(); j++) { + HeapNode heapNode = heapNodes.get(j); + HeapNode parent = heapNodes.get((j+1)/2 - 1); + heapNode.myParent = parent; + parent.myChildren[(j+1) % 2] = heapNode; + } + for (int j = heapNodes.size() / 2 - 1; j >= 0; j--) { + heapify(heapNodes.get(j)); + } + root.myChildren[2] = heapNodes.get(0); + root.myChildren[2].myParent = root; + } + } + } + + private void buildMainHeaps() { + myHeaps = new HashMap>(); + for (Node node : mySortedNodes) { + myProgressIndicator.checkCanceled(); + HeapNode outRoot = myOutRoots.get(node); + Node next = myNextNodes.get(node); + if (outRoot == null) { + if (next != null) { + myHeaps.put(node, myHeaps.get(next)); + } + continue; + } + + final Heap nextHeap = myHeaps.get(next); + if (nextHeap == null) { + myHeaps.put(node, new Heap(outRoot)); + continue; + } + + final Heap tHeap = nextHeap.insert(outRoot); + myHeaps.put(node, tHeap); + } + } + + private void heapify(HeapNode node) { + while (true) { + HeapNode min = node; + for (int i = 0; i < 2; i++) { + HeapNode child = node.myChildren[i]; + if (child != null && child.myEdge.getDelta() < min.myEdge.getDelta()) { + min = child; + } + } + if (min != node) { + GraphEdge t = min.myEdge; + min.myEdge = node.myEdge; + node.myEdge = t; + node = min; + } + else { + break; + } + } + } + + public List> findShortestPaths(int k) { + try { + if (myStart.equals(myFinish)) { + return Collections.singletonList(Collections.singletonList(myStart)); + } + computeDistancesToTarget(); + if (!myNextNodes.containsKey(myStart)) { + return Collections.emptyList(); + } + buildOutHeaps(); + buildMainHeaps(); + + PriorityQueue> queue = new PriorityQueue>(); + List>> sidetracks = new ArrayList>>(); + sidetracks.add(FList.>emptyList()); + + final Heap heap = myHeaps.get(myStart); + if (heap != null) { + queue.add(new Sidetracks(0, FList.>emptyList().prepend(heap.getRoot()))); + for (int i = 2; i <= k; i++) { + if (queue.isEmpty()) break; + myProgressIndicator.checkCanceled(); + final Sidetracks current = queue.remove(); + sidetracks.add(current.myEdges); + final HeapNode e = current.myEdges.getHead(); + final Heap next = myHeaps.get(e.myEdge.getFinish()); + if (next != null) { + final HeapNode f = next.getRoot(); + queue.add(new Sidetracks(current.myLength + f.myEdge.getDelta(), current.myEdges.prepend(f))); + } + for (HeapNode child : e.myChildren) { + if (child != null) { + queue.add(new Sidetracks(current.myLength - e.myEdge.getDelta() + child.myEdge.getDelta(), + current.myEdges.getTail().prepend(child))); + } + } + } + } + + return computePathsBySidetracks(sidetracks); + } + catch (ProcessCanceledException e) { + return Collections.emptyList(); + } + } + + private List> computePathsBySidetracks(List>> sidetracks) { + final List> result = new ArrayList>(); + for (FList> sidetrack : sidetracks) { + myProgressIndicator.checkCanceled(); + List> edges = new ArrayList>(); + while (!sidetrack.isEmpty()) { + edges.add(sidetrack.getHead().myEdge); + sidetrack = sidetrack.getTail(); + } + Node current = myStart; + final List path = new ArrayList(); + path.add(current); + int i = edges.size() - 1; + while (!current.equals(myFinish) || i >= 0) { + if (i >= 0 && edges.get(i).getStart().equals(current)) { + current = edges.get(i).getFinish(); + i--; + } + else { + current = myNextNodes.get(current); + LOG.assertTrue(current != null); + } + path.add(current); + } + result.add(path); + } + + return result; + } + + private static class Sidetracks implements Comparable { + private int myLength; + private final FList> myEdges; + + private Sidetracks(int length, FList> edges) { + myLength = length; + myEdges = edges; + } + + @Override + public int compareTo(Sidetracks o) { + return myLength - o.myLength; + } + } + + private static class Heap { + private final int mySize; + private HeapNode myRoot; + + public Heap(HeapNode root) { + myRoot = root; + mySize = 1; + } + + private Heap(int size, HeapNode root) { + mySize = size; + myRoot = root; + } + + public HeapNode getRoot() { + return myRoot; + } + + public Heap insert(HeapNode node) { + int pos = mySize + 1; + int pow = 1; + while (pos > pow << 2) { + pow <<= 1; + } + HeapNode place = myRoot; + while (true) { + final int ind = (pos & pow) != 0 ? 1 : 0; + if (pow == 1) { + HeapNode placeCopy = place.copy(); + placeCopy.myChildren[ind] = node; + node.myParent = placeCopy; + break; + } + place = place.myChildren[ind]; + pow >>= 1; + } + while (true) { + final HeapNode parent = node.myParent; + if (parent == null || parent.myEdge.getDelta() < node.myEdge.getDelta()) { + break; + } + final HeapNode parentCopy = parent.copy(); + final GraphEdge t = parentCopy.myEdge; + parentCopy.myEdge = node.myEdge; + node.myEdge = t; + final HeapNode t2 = parentCopy.myChildren[2]; + parentCopy.myChildren[2] = node.myChildren[2]; + node.myChildren[2] = t2; + node = parentCopy; + } + HeapNode newRoot = node; + while (newRoot.myParent != null) { + newRoot = newRoot.myParent; + } + return new Heap(mySize + 1, newRoot); + } + } + + private static class HeapNode { + public HeapNode[] myChildren; + public HeapNode myParent; + public GraphEdge myEdge; + + private HeapNode(GraphEdge edge) { + myEdge = edge; + myChildren = new HeapNode[3]; + } + + public HeapNode(HeapNode node) { + myEdge = node.myEdge; + myChildren = node.myChildren.clone(); + myParent = node.myParent; + } + + public HeapNode copy() { + return new HeapNode(this); + } + } +} diff --git a/platform/platform-impl/src/com/intellij/util/graph/impl/ShortestPathFinder.java b/platform/platform-impl/src/com/intellij/util/graph/impl/ShortestPathFinder.java new file mode 100644 index 000000000000..a676647e687c --- /dev/null +++ b/platform/platform-impl/src/com/intellij/util/graph/impl/ShortestPathFinder.java @@ -0,0 +1,70 @@ +/* + * Copyright 2000-2011 JetBrains s.r.o. + * + * 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 com.intellij.util.graph.impl; + +import com.intellij.util.graph.Graph; +import org.jetbrains.annotations.Nullable; + +import java.util.*; + +/** + * @author nik + */ +public class ShortestPathFinder { + private final Graph myGraph; + + public ShortestPathFinder(Graph graph) { + myGraph = graph; + } + + + @Nullable + public List findPath(Node start, Node finish) { + Map nextNodes = new HashMap(); + Deque queue = new ArrayDeque(); + queue.addLast(finish); + + boolean found = false; + while (!queue.isEmpty()) { + final Node node = queue.removeFirst(); + if (node.equals(start)) { + found = true; + break; + } + + final Iterator in = myGraph.getIn(node); + while (in.hasNext()) { + Node prev = in.next(); + if (!nextNodes.containsKey(prev)) { + nextNodes.put(prev, node); + queue.addLast(prev); + } + } + } + + if (!found) { + return null; + } + List path = new ArrayList(); + Node current = start; + while (!current.equals(finish)) { + path.add(current); + current = nextNodes.get(current); + } + path.add(finish); + return path; + } +} diff --git a/platform/platform-impl/testSrc/com/intellij/util/graph/GraphTestCase.java b/platform/platform-impl/testSrc/com/intellij/util/graph/GraphTestCase.java new file mode 100644 index 000000000000..ab14963613fb --- /dev/null +++ b/platform/platform-impl/testSrc/com/intellij/util/graph/GraphTestCase.java @@ -0,0 +1,58 @@ +/* + * Copyright 2000-2011 JetBrains s.r.o. + * + * 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 com.intellij.util.graph; + +import junit.framework.TestCase; + +import java.util.*; + +/** + * @author nik + */ +public abstract class GraphTestCase extends TestCase { + protected static Graph initGraph(final Map graph) { + final Map> out = new HashMap>(); + final Map> in = new HashMap>(); + for (String s : graph.keySet()) { + out.put(s, new ArrayList()); + in.put(s, new ArrayList()); + } + for (Map.Entry entry : graph.entrySet()) { + String from = entry.getKey(); + for (int i = 0; i < entry.getValue().length(); i++) { + String to = String.valueOf(entry.getValue().charAt(i)); + out.get(from).add(to); + in.get(to).add(from); + } + } + return new Graph() { + @Override + public Collection getNodes() { + return graph.keySet(); + } + + @Override + public Iterator getIn(final String n) { + return in.get(n).iterator(); + } + + @Override + public Iterator getOut(String n) { + return out.get(n).iterator(); + } + }; + } +} diff --git a/platform/platform-impl/testSrc/com/intellij/util/graph/KShortestPathsFinderTest.java b/platform/platform-impl/testSrc/com/intellij/util/graph/KShortestPathsFinderTest.java new file mode 100644 index 000000000000..4750a7c0fa49 --- /dev/null +++ b/platform/platform-impl/testSrc/com/intellij/util/graph/KShortestPathsFinderTest.java @@ -0,0 +1,168 @@ +/* + * Copyright 2000-2011 JetBrains s.r.o. + * + * 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 com.intellij.util.graph; + +import com.intellij.openapi.progress.EmptyProgressIndicator; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.testFramework.UsefulTestCase; +import com.intellij.util.graph.impl.KShortestPathsFinder; + +import java.util.*; + +/** + * @author nik + */ +public class KShortestPathsFinderTest extends GraphTestCase { + public void testEmpty() { + Map graph = new HashMap(); + graph.put("s", ""); + graph.put("t", ""); + doTest(graph); + } + + public void testOneEdge() { + final Map graph = new HashMap(); + graph.put("s", "t"); + graph.put("t", ""); + doTest(graph, "st"); + } + + public void testNoPaths() { + final Map graph = new HashMap(); + graph.put("s", "a"); + graph.put("a", "s"); + graph.put("b", "at"); + graph.put("t", "sab"); + doTest(graph); + } + + public void testOneVertex() { + Map graph = new HashMap(); + graph.put("s", ""); + doTest(graph, "s", "s", 5, "s"); + } + + public void testTwoPaths() { + final Map graph = new HashMap(); + graph.put("s", "ta"); + graph.put("a", "t"); + graph.put("t", ""); + doTest(graph, "st", "sat"); + } + + public void testManyEdgesToTarget() { + final Map graph = new HashMap(); + graph.put("s", "a"); + graph.put("a", "bt"); + graph.put("b", "ct"); + graph.put("c", "dt"); + graph.put("d", "t"); + graph.put("t", ""); + doTest(graph, "sat", "sabt", "sabct", "sabcdt"); + } + + public void testManyEdgesFromSource() { + final Map graph = new HashMap(); + graph.put("s", "abcdt"); + graph.put("a", "b"); + graph.put("b", "c"); + graph.put("c", "d"); + graph.put("d", "t"); + graph.put("t", ""); + doTest(graph, "st", "sdt", "scdt", "sbcdt", "sabcdt"); + } + + public void testTwoParts() { + final Map graph = new HashMap(); + graph.put("s", "ab"); + graph.put("a", "b"); + graph.put("b", "cd"); + graph.put("c", "t"); + graph.put("d", "e"); + graph.put("e", "t"); + graph.put("t", ""); + doTest(graph, "sbct", "sabct", "sbdet", "sabdet"); + } + + public void testHangingEdges() { + final Map graph = new HashMap(); + graph.put("s", "ae"); + graph.put("a", "bc"); + graph.put("b", "ac"); + graph.put("c", "ab"); + graph.put("d", "s"); + graph.put("e", "t"); + graph.put("t", ""); + doTest(graph, "set"); + } + + public void testSimpleCycle() { + final Map graph = new HashMap(); + graph.put("s", "t"); + graph.put("t", "s"); + doTest(graph, 4, "st", "stst", "ststst", "stststst"); + } + + public void testComplexCycle() { + final Map graph = new HashMap(); + graph.put("s", "p"); + graph.put("p", "qt"); + graph.put("q", "vt"); + graph.put("v", "p"); + graph.put("t", ""); + doTest(graph, 5, "spt", "spqt", "spqvpt", "spqvpqt", "spqvpqvpt"); + } + + public void testHeap() { + final Map graph = new HashMap(); + graph.put("s", "a"); + graph.put("a", "bd"); + graph.put("b", "cd"); + graph.put("c", "td"); + graph.put("d", "e"); + graph.put("e", "f"); + graph.put("f", "t"); + graph.put("t", ""); + doTest(graph, "sabct", "sadeft", "sabdeft", "sabcdeft"); + + } + + private static void doTest(Map graph, String... expectedPaths) { + doTest(graph, 10, expectedPaths); + } + + private static void doTest(Map graph, final int k, String... expectedPaths) { + doTest(graph, "s", "t", k, expectedPaths); + } + + private static void doTest(Map graph, final String start, final String finish, final int k, String... expectedPaths) { + final Graph generator = initGraph(graph); + final KShortestPathsFinder finder = new KShortestPathsFinder(generator, start, finish, new EmptyProgressIndicator()); + final List> paths = finder.findShortestPaths(k); + List pathStrings = new ArrayList(); + Set sizes = new HashSet(); + for (List path : paths) { + pathStrings.add(StringUtil.join(path, "")); + sizes.add(path.size()); + } + if (sizes.size() != paths.size()) { + UsefulTestCase.assertSameElements(pathStrings, expectedPaths); + } + else { + UsefulTestCase.assertOrderedEquals(pathStrings, expectedPaths); + } + } +} diff --git a/platform/platform-impl/testSrc/com/intellij/util/graph/SearchCyclesTest.java b/platform/platform-impl/testSrc/com/intellij/util/graph/SearchCyclesTest.java new file mode 100644 index 000000000000..2fce8b661cbe --- /dev/null +++ b/platform/platform-impl/testSrc/com/intellij/util/graph/SearchCyclesTest.java @@ -0,0 +1,96 @@ +/* + * Copyright 2000-2009 JetBrains s.r.o. + * + * 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 com.intellij.util.graph; + +import com.intellij.util.graph.impl.CycleFinder; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.testFramework.UsefulTestCase; + +import java.util.*; + +/** + * User: anna + * Date: Feb 13, 2005 + */ +public class SearchCyclesTest extends GraphTestCase { + public void test1() throws Exception{ + final HashMap graph = new HashMap(); + graph.put("a", "bd"); + graph.put("b", "d"); + graph.put("c", "a"); + graph.put("d", "ac"); + + doTest(graph, "a", "da", "bdca"); + } + + public void test2() throws Exception{ + final HashMap graph = new HashMap(); + graph.put("a", "b"); + graph.put("b", "d"); + graph.put("c", "a"); + graph.put("d", "ce"); + graph.put("e", "d"); + + doTest(graph, "a", "bdca"); + } + + public void test3() throws Exception{ + final HashMap graph = new HashMap(); + graph.put("a", "bd"); + graph.put("b", "d"); + graph.put("d", "a"); + + doTest(graph, "a", "da"); + } + + public void test4() throws Exception { + final HashMap graph = new HashMap(); + graph.put("a", "b"); + graph.put("b", "d"); + graph.put("c", "a"); + graph.put("d", "ef"); + graph.put("e", "c"); + graph.put("f", "e"); + + doTest(graph, "a", "bdeca"); + } + + public void test5() throws Exception{ + final HashMap graph = new HashMap(); + graph.put("a", "be"); + graph.put("b", "d"); + graph.put("c", "a"); + graph.put("d", "ef"); + graph.put("e", "c"); + graph.put("f", "e"); + doTest(graph, "a", "bdeca", "eca"); + } + + private static void doTest(HashMap graph, final String node, String... expected) { + Graph stringGraph = initGraph(graph); + final Set> nodeCycles = new CycleFinder(stringGraph).getNodeCycles(node); + checkResult(expected, nodeCycles); + } + + private static void checkResult(String[] expected, Set> cycles) { + Set cycleStrings = new HashSet(); + for (List cycle : cycles) { + cycleStrings.add(StringUtil.join(cycle, "")); + } + UsefulTestCase.assertSameElements(cycleStrings, expected); + } +} diff --git a/platform/platform-impl/testSrc/com/intellij/util/graph/ShortestPathTest.java b/platform/platform-impl/testSrc/com/intellij/util/graph/ShortestPathTest.java new file mode 100644 index 000000000000..d5db1b8bc39b --- /dev/null +++ b/platform/platform-impl/testSrc/com/intellij/util/graph/ShortestPathTest.java @@ -0,0 +1,96 @@ +/* + * Copyright 2000-2009 JetBrains s.r.o. + * + * 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 com.intellij.util.graph; + +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.util.graph.impl.ShortestPathFinder; +import org.jetbrains.annotations.Nullable; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * User: anna + * Date: Feb 11, 2005 + */ +public class ShortestPathTest extends GraphTestCase { + public void testEmptyPath() { + final HashMap graph = new HashMap(); + graph.put("a", ""); + graph.put("b", ""); + doTest(graph, "a", "a", "a"); + } + + public void testNoPath() { + final HashMap graph = new HashMap(); + graph.put("a", "c"); + graph.put("b", "a"); + graph.put("c", "a"); + assertNull(getShortestPath(graph, "a", "b")); + } + + public void test1() { + final HashMap graph = new HashMap(); + graph.put("a", ""); + graph.put("b", "ac"); + graph.put("c", "ab"); + graph.put("d", "a"); + doTest(graph, "b", "a", "ba"); + } + + public void test2() { + final HashMap graph = new HashMap(); + graph.put("a", "cd"); + graph.put("b", "a"); + graph.put("c", "d"); + graph.put("d", "ab"); + doTest(graph, "c", "b", "cdb"); + } + + public void test3() { + final HashMap graph = new HashMap(); + graph.put("a", "bd"); + graph.put("b", "d"); + graph.put("c", "a"); + graph.put("d", "ac"); + doTest(graph, "b", "c", "bdc"); + } + + public void test4() { + final HashMap graph = new HashMap(); + graph.put("a", "be"); + graph.put("b", "d"); + graph.put("c", "a"); + graph.put("d", "ef"); + graph.put("e", "c"); + graph.put("f", "e"); + doTest(graph, "b", "c", "bdec"); + } + + private static void doTest(HashMap graph, final String from, final String to, final String expectedPath) { + final List shortestPath = getShortestPath(graph, from, to); + assertNotNull(shortestPath); + assertEquals(expectedPath, StringUtil.join(shortestPath, "")); + } + + @Nullable + private static List getShortestPath(Map graph, final String from, final String to) { + Graph graphGenerator = initGraph(graph); + return new ShortestPathFinder(graphGenerator).findPath(from, to); + } +} diff --git a/platform/platform-resources/src/META-INF/PlatformExtensions.xml b/platform/platform-resources/src/META-INF/PlatformExtensions.xml index e86bcebba2cf..a3db039882de 100644 --- a/platform/platform-resources/src/META-INF/PlatformExtensions.xml +++ b/platform/platform-resources/src/META-INF/PlatformExtensions.xml @@ -96,6 +96,9 @@ + +