View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.project;
20  
21  import java.util.*;
22  
23  class Graph {
24      private enum DfsState {
25          VISITING,
26          VISITED
27      }
28  
29      final Map<String, Vertex> vertices = new LinkedHashMap<>();
30  
31      public Vertex getVertex(String id) {
32          return vertices.get(id);
33      }
34  
35      public Collection<Vertex> getVertices() {
36          return vertices.values();
37      }
38  
39      Vertex addVertex(String label) {
40          return vertices.computeIfAbsent(label, Vertex::new);
41      }
42  
43      void addEdge(Vertex from, Vertex to) throws CycleDetectedException {
44          from.children.add(to);
45          to.parents.add(from);
46          List<String> cycle = findCycle(to);
47          if (cycle != null) {
48              // remove edge which introduced cycle
49              removeEdge(from, to);
50              throw new CycleDetectedException(
51                      "Edge between '" + from.label + "' and '" + to.label + "' introduces to cycle in the graph", cycle);
52          }
53      }
54  
55      void removeEdge(Vertex from, Vertex to) {
56          from.children.remove(to);
57          to.parents.remove(from);
58      }
59  
60      List<String> visitAll() {
61          return visitAll(vertices.values(), new HashMap<>(), new ArrayList<>());
62      }
63  
64      List<String> findCycle(Vertex vertex) {
65          return visitCycle(Collections.singleton(vertex), new HashMap<>(), new LinkedList<>());
66      }
67  
68      private static List<String> visitAll(
69              Collection<Vertex> children, Map<Vertex, DfsState> stateMap, List<String> list) {
70          for (Vertex v : children) {
71              DfsState state = stateMap.putIfAbsent(v, DfsState.VISITING);
72              if (state == null) {
73                  visitAll(v.children, stateMap, list);
74                  stateMap.put(v, DfsState.VISITED);
75                  list.add(v.label);
76              }
77          }
78          return list;
79      }
80  
81      private static List<String> visitCycle(
82              Collection<Vertex> children, Map<Vertex, DfsState> stateMap, LinkedList<String> cycle) {
83          for (Vertex v : children) {
84              DfsState state = stateMap.putIfAbsent(v, DfsState.VISITING);
85              if (state == null) {
86                  cycle.addLast(v.label);
87                  List<String> ret = visitCycle(v.children, stateMap, cycle);
88                  if (ret != null) {
89                      return ret;
90                  }
91                  cycle.removeLast();
92                  stateMap.put(v, DfsState.VISITED);
93              } else if (state == DfsState.VISITING) {
94                  // we are already visiting this vertex, this mean we have a cycle
95                  int pos = cycle.lastIndexOf(v.label);
96                  List<String> ret = cycle.subList(pos, cycle.size());
97                  ret.add(v.label);
98                  return ret;
99              }
100         }
101         return null;
102     }
103 
104     static class Vertex {
105         final String label;
106         final List<Vertex> children = new ArrayList<>();
107         final List<Vertex> parents = new ArrayList<>();
108 
109         Vertex(String label) {
110             this.label = label;
111         }
112 
113         String getLabel() {
114             return label;
115         }
116 
117         List<Vertex> getChildren() {
118             return children;
119         }
120 
121         List<Vertex> getParents() {
122             return parents;
123         }
124     }
125 }