-
Notifications
You must be signed in to change notification settings - Fork 79
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
7ccdf9d
commit 48c346e
Showing
3 changed files
with
371 additions
and
0 deletions.
There are no files selected for viewing
105 changes: 105 additions & 0 deletions
105
nop-core/src/main/java/io/nop/core/model/graph/dag/Dag.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
package io.nop.core.model.graph.dag; | ||
|
||
import io.nop.api.core.annotations.data.DataBean; | ||
import io.nop.api.core.exceptions.NopException; | ||
import io.nop.core.model.tree.impl.WidthFirstIterator; | ||
|
||
import java.util.Collections; | ||
import java.util.HashMap; | ||
import java.util.Iterator; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.Set; | ||
import java.util.function.Consumer; | ||
import java.util.stream.Collectors; | ||
|
||
import static io.nop.core.CoreErrors.ARG_NAME; | ||
import static io.nop.core.CoreErrors.ERR_GRAPH_UNKNOWN_NODE; | ||
|
||
@DataBean | ||
public class Dag { | ||
private String rootNodeName; | ||
private Map<String, DagNode> nodes = new HashMap<>(); | ||
|
||
public Dag() { | ||
} | ||
|
||
public Dag(String rootName) { | ||
setRootNodeName(rootName); | ||
nodes.put(rootName, new DagNode(rootName)); | ||
} | ||
|
||
public Iterator<DagNode> widthFirstIterator() { | ||
return new WidthFirstIterator<>(this::getNextNodes, getRootNode(), true, null); | ||
} | ||
|
||
public List<DagNode> getNextNodes(DagNode node) { | ||
Set<String> nextNames = node.getNextNodeNames(); | ||
if (nextNames == null || nextNames.isEmpty()) | ||
return Collections.emptyList(); | ||
|
||
return nextNames.stream().map(this::requireNode).collect(Collectors.toList()); | ||
} | ||
|
||
public DagNode getRootNode() { | ||
return requireNode(rootNodeName); | ||
} | ||
|
||
public boolean isEmpty() { | ||
return nodes.isEmpty(); | ||
} | ||
|
||
public void forEachNode(Consumer<DagNode> action) { | ||
this.nodes.values().forEach(action); | ||
} | ||
|
||
public int size() { | ||
return nodes.size(); | ||
} | ||
|
||
public DagNode getNode(String name) { | ||
return nodes.get(name); | ||
} | ||
|
||
public DagNode requireNode(String name) { | ||
DagNode node = getNode(name); | ||
if (node == null) | ||
throw new NopException(ERR_GRAPH_UNKNOWN_NODE) | ||
.param(ARG_NAME, name); | ||
return node; | ||
} | ||
|
||
public void analyze() { | ||
new DagAnalyzer(this).analyze(); | ||
} | ||
|
||
public DagNode addNextNodes(String nodeName, Set<String> next) { | ||
DagNode node = nodes.computeIfAbsent(nodeName, DagNode::new); | ||
if (next != null) { | ||
node.addNextNodes(next); | ||
} | ||
return node; | ||
} | ||
|
||
public DagNode addNextNode(String nodeName, String next) { | ||
DagNode node = nodes.computeIfAbsent(nodeName, DagNode::new); | ||
node.addNextNode(next); | ||
return node; | ||
} | ||
|
||
public String getRootNodeName() { | ||
return rootNodeName; | ||
} | ||
|
||
public void setRootNodeName(String rootNodeName) { | ||
this.rootNodeName = rootNodeName; | ||
} | ||
|
||
public Map<String, DagNode> getNodes() { | ||
return nodes; | ||
} | ||
|
||
public void setNodes(Map<String, DagNode> nodes) { | ||
this.nodes = nodes; | ||
} | ||
} |
68 changes: 68 additions & 0 deletions
68
nop-core/src/main/java/io/nop/core/model/graph/dag/DagAnalyzer.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
package io.nop.core.model.graph.dag; | ||
|
||
import io.nop.api.core.exceptions.NopException; | ||
import io.nop.commons.collections.bit.IBitSet; | ||
import io.nop.commons.util.CollectionHelper; | ||
import io.nop.commons.util.objects.Pair; | ||
|
||
import java.util.ArrayList; | ||
import java.util.Iterator; | ||
import java.util.List; | ||
|
||
import static io.nop.core.CoreErrors.ARG_NODE_NAMES; | ||
import static io.nop.core.CoreErrors.ERR_GRAPH_NODES_NOT_REACHABLE; | ||
|
||
/** | ||
* 原始的图结构中有可能包含环,分析之后会删除一些连接使其成为环 | ||
*/ | ||
public class DagAnalyzer { | ||
private final Dag dag; | ||
|
||
private final DagNode[] nodes; | ||
|
||
// 删除哪些next连接才能得到一个无循环的DAG结构。 | ||
private List<Pair<String, String>> removedLinks = new ArrayList<>(); | ||
|
||
private final IBitSet analyzedFlags; | ||
|
||
public DagAnalyzer(Dag dag) { | ||
this.dag = dag; | ||
this.nodes = new DagNode[dag.size()]; | ||
this.analyzedFlags = CollectionHelper.newFixedBitSet(dag.size()); | ||
} | ||
|
||
public void analyze() { | ||
checkAllReachable(); | ||
checkLoop(); | ||
} | ||
|
||
private void checkAllReachable() { | ||
int index = 0; | ||
Iterator<DagNode> it = dag.widthFirstIterator(); | ||
while (it.hasNext()) { | ||
DagNode node = it.next(); | ||
this.nodes[index] = node; | ||
node.setNodeIndex(index++); | ||
} | ||
|
||
if (index != dag.size()) { | ||
// 通过宽度优先遍历无法达到所有节点 | ||
throw new NopException(ERR_GRAPH_NODES_NOT_REACHABLE) | ||
.param(ARG_NODE_NAMES, getUnreachableNodes()); | ||
} | ||
} | ||
|
||
private void checkLoop() { | ||
DagNode root = nodes[0]; | ||
|
||
} | ||
|
||
private List<String> getUnreachableNodes() { | ||
List<String> ret = new ArrayList<>(); | ||
dag.forEachNode(node -> { | ||
if (node.getNodeIndex() <= 0) | ||
ret.add(node.getName()); | ||
}); | ||
return ret; | ||
} | ||
} |
198 changes: 198 additions & 0 deletions
198
nop-core/src/main/java/io/nop/core/model/graph/dag/DagNode.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,198 @@ | ||
package io.nop.core.model.graph.dag; | ||
|
||
import io.nop.api.core.annotations.data.DataBean; | ||
|
||
import java.util.LinkedHashSet; | ||
import java.util.Set; | ||
|
||
@DataBean | ||
public class DagNode implements Comparable<DagNode> { | ||
private String name; | ||
|
||
/** | ||
* 如果是从根节点可达的节点,则它的nodeIndex会被设置为宽度遍历的序号 | ||
*/ | ||
private int nodeIndex = -1; | ||
|
||
private boolean internal; | ||
|
||
private String controlNodeName; | ||
|
||
private Set<String> nextNodeNames; | ||
private Set<String> prevNodeNames; | ||
|
||
private Set<String> nextNormalNodeNames; | ||
|
||
private Set<String> prevNormalNodeNames; | ||
|
||
private boolean nextToEnd; | ||
|
||
private boolean eventuallyToEnd; | ||
|
||
private boolean nextToEmpty; | ||
|
||
private boolean eventuallyToEmpty; | ||
|
||
private boolean nextToAssigned; | ||
|
||
private boolean eventuallyToAssigned; | ||
|
||
public DagNode() { | ||
} | ||
|
||
public DagNode(String name) { | ||
setName(name); | ||
} | ||
|
||
@Override | ||
public int compareTo(DagNode o) { | ||
return Integer.compare(this.nodeIndex, o.nodeIndex); | ||
} | ||
|
||
public void addNextNodes(Set<String> next) { | ||
if (next == null) | ||
return; | ||
|
||
if (this.nextNodeNames == null) | ||
this.nextNodeNames = new LinkedHashSet<>(); | ||
this.nextNodeNames.addAll(next); | ||
} | ||
|
||
public void addNextNode(String next) { | ||
if (next == null) | ||
return; | ||
if (this.nextNodeNames == null) | ||
this.nextNodeNames = new LinkedHashSet<>(); | ||
this.nextNodeNames.add(next); | ||
} | ||
|
||
public void addPrevNodes(Set<String> prev) { | ||
if (prev == null) | ||
return; | ||
|
||
if (this.prevNodeNames == null) | ||
this.prevNodeNames = new LinkedHashSet<>(); | ||
this.prevNodeNames.addAll(prev); | ||
} | ||
|
||
public void addPrevNode(String prev) { | ||
if (prev == null) | ||
return; | ||
|
||
if (this.prevNodeNames == null) | ||
this.prevNodeNames = new LinkedHashSet<>(); | ||
this.prevNodeNames.add(prev); | ||
} | ||
|
||
public String getName() { | ||
return name; | ||
} | ||
|
||
public void setName(String name) { | ||
this.name = name; | ||
} | ||
|
||
public int getNodeIndex() { | ||
return nodeIndex; | ||
} | ||
|
||
public void setNodeIndex(int nodeIndex) { | ||
this.nodeIndex = nodeIndex; | ||
} | ||
|
||
public boolean isInternal() { | ||
return internal; | ||
} | ||
|
||
public void setInternal(boolean internal) { | ||
this.internal = internal; | ||
} | ||
|
||
public String getControlNodeName() { | ||
return controlNodeName; | ||
} | ||
|
||
public void setControlNodeName(String controlNodeName) { | ||
this.controlNodeName = controlNodeName; | ||
} | ||
|
||
public Set<String> getNextNodeNames() { | ||
return nextNodeNames; | ||
} | ||
|
||
public void setNextNodeNames(Set<String> nextNodeNames) { | ||
this.nextNodeNames = nextNodeNames; | ||
} | ||
|
||
public Set<String> getPrevNodeNames() { | ||
return prevNodeNames; | ||
} | ||
|
||
public void setPrevNodeNames(Set<String> prevNodeNames) { | ||
this.prevNodeNames = prevNodeNames; | ||
} | ||
|
||
public Set<String> getNextNormalNodeNames() { | ||
return nextNormalNodeNames; | ||
} | ||
|
||
public void setNextNormalNodeNames(Set<String> nextNormalNodeNames) { | ||
this.nextNormalNodeNames = nextNormalNodeNames; | ||
} | ||
|
||
public Set<String> getPrevNormalNodeNames() { | ||
return prevNormalNodeNames; | ||
} | ||
|
||
public void setPrevNormalNodeNames(Set<String> prevNormalNodeNames) { | ||
this.prevNormalNodeNames = prevNormalNodeNames; | ||
} | ||
|
||
public boolean isNextToEnd() { | ||
return nextToEnd; | ||
} | ||
|
||
public void setNextToEnd(boolean nextToEnd) { | ||
this.nextToEnd = nextToEnd; | ||
} | ||
|
||
public boolean isEventuallyToEnd() { | ||
return eventuallyToEnd; | ||
} | ||
|
||
public void setEventuallyToEnd(boolean eventuallyToEnd) { | ||
this.eventuallyToEnd = eventuallyToEnd; | ||
} | ||
|
||
public boolean isNextToEmpty() { | ||
return nextToEmpty; | ||
} | ||
|
||
public void setNextToEmpty(boolean nextToEmpty) { | ||
this.nextToEmpty = nextToEmpty; | ||
} | ||
|
||
public boolean isEventuallyToEmpty() { | ||
return eventuallyToEmpty; | ||
} | ||
|
||
public void setEventuallyToEmpty(boolean eventuallyToEmpty) { | ||
this.eventuallyToEmpty = eventuallyToEmpty; | ||
} | ||
|
||
public boolean isNextToAssigned() { | ||
return nextToAssigned; | ||
} | ||
|
||
public void setNextToAssigned(boolean nextToAssigned) { | ||
this.nextToAssigned = nextToAssigned; | ||
} | ||
|
||
public boolean isEventuallyToAssigned() { | ||
return eventuallyToAssigned; | ||
} | ||
|
||
public void setEventuallyToAssigned(boolean eventuallyToAssigned) { | ||
this.eventuallyToAssigned = eventuallyToAssigned; | ||
} | ||
} |