View Javadoc
1   package org.codehaus.plexus.util.dag;
2   
3   /*
4    * Copyright The Codehaus Foundation.
5    *
6    * Licensed under the Apache License, Version 2.0 (the "License");
7    * you may not use this file except in compliance with the License.
8    * 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, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  
19  import java.util.Collections;
20  import java.util.HashMap;
21  import java.util.LinkedList;
22  import java.util.List;
23  import java.util.Map;
24  
25  /**
26   * @author <a href="michal.maczka@dimatics.com">Michal Maczka</a>
27   *
28   */
29  public class CycleDetector
30  {
31  
32      private final static Integer NOT_VISITED = 0;
33  
34      private final static Integer VISITING = 1;
35  
36      private final static Integer VISITED = 2;
37  
38      public static List<String> hasCycle( final DAG graph )
39      {
40          final List<Vertex> vertices = graph.getVertices();
41  
42          final Map<Vertex, Integer> vertexStateMap = new HashMap<>();
43  
44          List<String> retValue = null;
45  
46          for ( Vertex vertex : vertices )
47          {
48              if ( isNotVisited( vertex, vertexStateMap ) )
49              {
50                  retValue = introducesCycle( vertex, vertexStateMap );
51  
52                  if ( retValue != null )
53                  {
54                      break;
55                  }
56              }
57          }
58  
59          return retValue;
60      }
61  
62      /**
63       * This method will be called when an edge leading to given vertex was added and we want to check if introduction of
64       * this edge has not resulted in apparition of cycle in the graph
65       *
66       * @param vertex the vertex
67       * @param vertexStateMap the vertex Map
68       * @return the found cycle
69       */
70      public static List<String> introducesCycle( final Vertex vertex, final Map<Vertex, Integer> vertexStateMap )
71      {
72          final LinkedList<String> cycleStack = new LinkedList<>();
73  
74          final boolean hasCycle = dfsVisit( vertex, cycleStack, vertexStateMap );
75  
76          if ( hasCycle )
77          {
78              // we have a situation like: [b, a, c, d, b, f, g, h].
79              // Label of Vertex which introduced the cycle is at the first position in the list
80              // We have to find second occurrence of this label and use its position in the list
81              // for getting the sublist of vertex labels of cycle participants
82              //
83              // So in our case we are searching for [b, a, c, d, b]
84              final String label = cycleStack.getFirst();
85  
86              final int pos = cycleStack.lastIndexOf( label );
87  
88              final List<String> cycle = cycleStack.subList( 0, pos + 1 );
89  
90              Collections.reverse( cycle );
91  
92              return cycle;
93          }
94  
95          return null;
96      }
97  
98      public static List<String> introducesCycle( final Vertex vertex )
99      {
100         final Map<Vertex, Integer> vertexStateMap = new HashMap<>();
101 
102         return introducesCycle( vertex, vertexStateMap );
103     }
104 
105     private static boolean isNotVisited( final Vertex vertex, final Map<Vertex, Integer> vertexStateMap )
106     {
107         final Integer state = vertexStateMap.get( vertex );
108 
109         return ( state == null ) || NOT_VISITED.equals( state );
110     }
111 
112     private static boolean isVisiting( final Vertex vertex, final Map<Vertex, Integer> vertexStateMap )
113     {
114         final Integer state = vertexStateMap.get( vertex );
115 
116         return VISITING.equals( state );
117     }
118 
119     private static boolean dfsVisit( final Vertex vertex, final LinkedList<String> cycle,
120                                      final Map<Vertex, Integer> vertexStateMap )
121     {
122         cycle.addFirst( vertex.getLabel() );
123 
124         vertexStateMap.put( vertex, VISITING );
125 
126         for ( Vertex v : vertex.getChildren() )
127         {
128             if ( isNotVisited( v, vertexStateMap ) )
129             {
130                 final boolean hasCycle = dfsVisit( v, cycle, vertexStateMap );
131 
132                 if ( hasCycle )
133                 {
134                     return true;
135                 }
136             }
137             else if ( isVisiting( v, vertexStateMap ) )
138             {
139                 cycle.addFirst( v.getLabel() );
140 
141                 return true;
142             }
143         }
144         vertexStateMap.put( vertex, VISITED );
145 
146         cycle.removeFirst();
147 
148         return false;
149     }
150 
151 }