/**
* Copyright 2012-2017 Gunnar Morling (http://www.gunnarmorling.de/)
* and/or other contributors as indicated by the @authors tag. See the
* copyright.txt file in the distribution for a full listing of all
* contributors.
*
* 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.mapstruct.ap.internal.model.dependency;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
/**
* Analyzes graphs: Discovers all descendants of given nodes and detects cyclic dependencies between nodes if present.
*
* @author Gunnar Morling
*/
public class GraphAnalyzer {
private final Map<String, Node> nodes;
private final Set<List<String>> cycles;
private final Stack<Node> currentPath;
private int nextTraversalSequence = 0;
private GraphAnalyzer(Map<String, Node> nodes) {
this.nodes = nodes;
cycles = new HashSet<List<String>>();
currentPath = new Stack<Node>();
}
public static GraphAnalyzerBuilder builder() {
return new GraphAnalyzerBuilder();
}
public static GraphAnalyzerBuilder withNode(String name, String... descendants) {
return builder().withNode( name, descendants );
}
/**
* Performs a full traversal of the graph, detecting potential cycles and calculates the full list of descendants of
* the nodes.
*/
private void analyze() {
for ( Node node : nodes.values() ) {
depthFirstSearch( node );
}
}
/**
* Returns the traversal sequence number of the given node. The ascending order of the traversal sequence numbers of
* multiple nodes represents the depth-first traversal order of those nodes.
* <p>
* <b>Note</b>: The traversal sequence numbers will only be complete if the graph contains no cycles.
*
* @param name the node name to get the traversal sequence number for
* @return the traversal sequence number, or {@code -1} if the node doesn't exist or the node was not visited (in
* case of cycles).
*/
public int getTraversalSequence(String name) {
Node node = nodes.get( name );
return node != null ? node.getTraversalSequence() : -1;
}
public Set<List<String>> getCycles() {
return cycles;
}
private void depthFirstSearch(Node node) {
if ( node.isProcessed() ) {
return;
}
currentPath.push( node );
// the node is on the stack already -> cycle
if ( node.isVisited() ) {
cycles.add( getCurrentCycle( node ) );
currentPath.pop();
return;
}
node.setVisited( true );
for ( Node descendant : node.getDescendants() ) {
depthFirstSearch( descendant );
}
node.setTraversalSequence( nextTraversalSequence++ );
currentPath.pop();
}
private List<String> getCurrentCycle(Node start) {
List<String> cycle = new ArrayList<String>();
boolean inCycle = false;
for ( Node n : currentPath ) {
if ( !inCycle && n.equals( start ) ) {
inCycle = true;
}
if ( inCycle ) {
cycle.add( n.getName() );
}
}
return cycle;
}
public static class GraphAnalyzerBuilder {
private final Map<String, Node> nodes = new LinkedHashMap<String, Node>();
public GraphAnalyzerBuilder withNode(String name, List<String> descendants) {
Node node = getNode( name );
for ( String descendant : descendants ) {
node.addDescendant( getNode( descendant ) );
}
return this;
}
public GraphAnalyzerBuilder withNode(String name, String... descendants) {
return withNode( name, Arrays.asList( descendants ) );
}
/**
* Builds the analyzer and triggers traversal of all nodes for detecting potential cycles and calculates the
* full list of descendants of each node.
*
* @return the analyzer
*/
public GraphAnalyzer build() {
GraphAnalyzer graphAnalyzer = new GraphAnalyzer( nodes );
graphAnalyzer.analyze();
return graphAnalyzer;
}
private Node getNode(String name) {
Node node = nodes.get( name );
if ( node == null ) {
node = new Node( name );
nodes.put( name, node );
}
return node;
}
}
}