/* * The Alluxio Open Foundation licenses this work under the Apache License, version 2.0 * (the "License"). You may not use this work except in compliance with the License, which is * available at www.apache.org/licenses/LICENSE-2.0 * * This software is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied, as more fully set forth in the License. * * See the NOTICE file distributed with this work for information regarding copyright ownership. */ package alluxio.collections; import com.google.common.base.Preconditions; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Deque; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.annotation.concurrent.NotThreadSafe; /** * A Directed Acyclic Graph (DAG). * * @param <T> the payload of each node */ @NotThreadSafe public class DirectedAcyclicGraph<T> { private final List<DirectedAcyclicGraphNode<T>> mRoots; private final Map<T, DirectedAcyclicGraphNode<T>> mIndex; /** * A Directed Acyclic Graph (DAG). */ public DirectedAcyclicGraph() { mRoots = new ArrayList<>(); mIndex = new HashMap<>(); } /** * Adds a node to the DAG, that takes the given payload and depends on the specified parents. * * @param payload the payload * @param parents the parents of the created node */ public void add(T payload, List<T> parents) { // This checks the payload doesn't exist in the graph, and therefore prevents cycles. Preconditions.checkState(!contains(payload), "the payload already exists in the DAG"); // construct the new node DirectedAcyclicGraphNode<T> newNode = new DirectedAcyclicGraphNode<>(payload); mIndex.put(payload, newNode); if (parents.isEmpty()) { // add to root mRoots.add(newNode); } else { // find parent nodes for (T parent : parents) { Preconditions.checkState(contains(parent), "the parent payload " + parent + " does not exist in the DAG"); DirectedAcyclicGraphNode<T> parentNode = mIndex.get(parent); parentNode.addChild(newNode); newNode.addParent(parentNode); } } } /** * Deletes a leaf DAG node that carries the given payload. * * @param payload the payload of the node to delete */ public void deleteLeaf(T payload) { Preconditions.checkState(contains(payload), "the node does not exist"); DirectedAcyclicGraphNode<T> node = mIndex.get(payload); Preconditions.checkState(node.getChildren().isEmpty(), "the node is not a leaf"); // delete from parent for (DirectedAcyclicGraphNode<T> parent : node.getParents()) { parent.removeChild(node); } // remove from index mIndex.remove(payload); if (node.getParents().isEmpty()) { mRoots.remove(node); } } /** * Checks if a node with the given payload is in the DAG. * * @param payload the payload of the node to check * @return true if there a node in the DAG contains the given value as payload, false otherwise */ public boolean contains(T payload) { return mIndex.containsKey(payload); } /** * Gets the payloads for the children of the given node. * * @param payload the payload of the parent node * @return the children's payloads, an empty list if the given payload doesn't exist in the DAG */ public List<T> getChildren(T payload) { List<T> children = new ArrayList<>(); if (!mIndex.containsKey(payload)) { return children; } DirectedAcyclicGraphNode<T> node = mIndex.get(payload); for (DirectedAcyclicGraphNode<T> child : node.getChildren()) { children.add(child.getPayload()); } return children; } /** * Gets the payloads for the given nodes parents. * * @param payload the payload of the children node * @return the parents' payloads, an empty list if the given payload doesn't exist in the DAG */ public List<T> getParents(T payload) { List<T> parents = new ArrayList<>(); if (!mIndex.containsKey(payload)) { return parents; } DirectedAcyclicGraphNode<T> node = mIndex.get(payload); for (DirectedAcyclicGraphNode<T> parent : node.getParents()) { parents.add(parent.getPayload()); } return parents; } /** * Checks if a given payload is in a root of the DAG. * * @param payload the payload to check for root * @return true if the payload is in the root of the DAG, false otherwise */ public boolean isRoot(T payload) { if (!contains(payload)) { return false; } return mRoots.contains(mIndex.get(payload)); } /** * Gets the payloads of all the root nodes of the DAG. * * @return all the root payloads */ public List<T> getRoots() { List<T> roots = new ArrayList<>(); for (DirectedAcyclicGraphNode<T> root : mRoots) { roots.add(root.getPayload()); } return roots; } /** * Sorts a given set of payloads topologically based on the DAG. This method requires all the * payloads to be in the DAG. * * @param payloads the set of input payloads * @return the payloads after topological sort */ public List<T> sortTopologically(Set<T> payloads) { List<T> result = new ArrayList<>(); Set<T> input = new HashSet<>(payloads); Deque<DirectedAcyclicGraphNode<T>> toVisit = new ArrayDeque<>(mRoots); while (!toVisit.isEmpty()) { DirectedAcyclicGraphNode<T> visit = toVisit.removeFirst(); T payload = visit.getPayload(); if (input.remove(payload)) { result.add(visit.getPayload()); } toVisit.addAll(visit.getChildren()); } Preconditions.checkState(input.isEmpty(), "Not all the given payloads are in the DAG: ", input); return result; } /** * Gets all payloads of the DAG in the topological order. * * @return the payloads of all the nodes in topological order */ public List<T> getAllInTopologicalOrder() { return sortTopologically(mIndex.keySet()); } }