
ru.taskurotta.service.dependency.links.Graph Maven / Gradle / Ivy
package ru.taskurotta.service.dependency.links;
import com.fasterxml.jackson.annotation.JsonIgnore;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
/**
* This is not thread safe object. It should be synchronized with service by version value.
*
* User: romario
* Date: 4/5/13
* Time: 11:35 AM
*/
@SuppressWarnings("UnusedDeclaration")
public class Graph implements Serializable {
private final static Logger logger = LoggerFactory.getLogger(Graph.class);
public final static UUID[] EMPTY_ARRAY = new UUID[0];
private int version;
private UUID graphId; //should be equal to process ID
/**
* Map of all not finished items in this process and its time of start in milliseconds.
* It has 0 value if item is not started yet.
*/
private Map notFinishedItems;
/**
* Links map where keys are tasks which depends from value set of other tasks.
* For example, A(B, C) - A is a key and {B, C} is a set value of map.
*/
private Map> links;
private Set finishedItems;
// modification stuff.
private Modification modification;
private UUID[] readyItems;
private long touchTimeMillis;
private long lastApplyTimeMillis;
/**
* generic constructor for deserializer
*/
public Graph() {
this.touchTimeMillis = System.currentTimeMillis();
this.version = 0;
this.notFinishedItems = new HashMap<>();
this.links = new HashMap<>();
this.finishedItems = new HashSet<>();
}
/**
* smart constructor for deserializer
*/
public Graph(int version, UUID graphId, Map notFinishedItems, Map> links,
Set finishedItems, long lastApplyTimeMillis, long touchTimeMillis) {
this.version = version;
this.graphId = graphId;
if (notFinishedItems != null) {
this.notFinishedItems = notFinishedItems;
} else {
this.notFinishedItems = new HashMap<>();
}
if (links != null) {
this.links = links;
} else {
this.links = new HashMap<>();
}
if (finishedItems != null) {
this.finishedItems = finishedItems;
} else {
this.finishedItems = new HashSet<>();
}
this.lastApplyTimeMillis = lastApplyTimeMillis;
this.touchTimeMillis = touchTimeMillis;
}
/**
* Create new graph
*
* @param graphId - should be equal to process ID
* @param startItem - ID of the first task in process
*/
public Graph(UUID graphId, UUID startItem) {
this();
this.graphId = graphId;
notFinishedItems.put(startItem, 0L);
}
private static Map> reverseIt(Map> links) {
Map> reverseResult = new HashMap<>();
if (links.isEmpty()) {
return reverseResult;
}
for (Map.Entry> entry : links.entrySet()) {
for (UUID toItem : entry.getValue()) {
Set fromItems = reverseResult.get(toItem);
if (fromItems == null) {
fromItems = new HashSet<>();
reverseResult.put(toItem, fromItems);
}
fromItems.add(entry.getKey());
}
}
return reverseResult;
}
public int getVersion() {
return version;
}
@JsonIgnore
public Modification getModification() {
return modification;
}
public boolean hasNotFinishedItem(UUID itemId) {
return notFinishedItems.containsKey(itemId);
}
public Map getNotFinishedItems() {
return notFinishedItems;
}
public Map> getLinks() {
return links;
}
public void setVersion(int version) {
this.version = version;
}
public void setNotFinishedItems(Map notFinishedItems) {
this.notFinishedItems = notFinishedItems;
}
public void setLinks(Map> links) {
this.links = links;
}
@JsonIgnore
public UUID[] getReadyItems() {
return readyItems;
}
public UUID getGraphId() {
return graphId;
}
public void setGraphId(UUID graphId) {
this.graphId = graphId;
}
@JsonIgnore
public boolean isFinished() {
return notFinishedItems.isEmpty();
}
public Set getFinishedItems() {
return finishedItems;
}
public void setFinishedItems(Set finishedItems) {
this.finishedItems = finishedItems;
}
/**
* Apply changes to the graph
*
* @param modification - diff object to apply
*/
public void apply(Modification modification) {
long currentTime = System.currentTimeMillis();
logger.debug("apply() modification = [{}]", modification);
this.modification = modification;
version++;
// process finished item
UUID finishedItem = modification.getCompletedItem();
notFinishedItems.remove(finishedItem);
finishedItems.add(finishedItem);
// get new items without dependencies
List readyItemsList = applyNewItems();
// update links collection and get release candidates
Set reverseItemLinks = updateLinks();
// update readyItemList with old items that become ready
findReadyItems(readyItemsList, reverseItemLinks);
// return empty or full array of new ready items.
readyItems = readyItemsList.toArray(new UUID[readyItemsList.size()]);
for (UUID readyItemsId : readyItems) {
notFinishedItems.put(readyItemsId, currentTime);
}
touchTimeMillis = lastApplyTimeMillis = currentTime;
}
public Map getAllReadyItems() {
Map readyItems = null;
for (UUID itemId : notFinishedItems.keySet()) {
if (links.get(itemId) != null) {
continue;
}
if (readyItems == null) {
readyItems = new HashMap<>();
}
readyItems.put(itemId, notFinishedItems.get(itemId));
}
return readyItems;
}
private List applyNewItems() {
List readyItemsList = new LinkedList<>();
Collection newItems = modification.getNewItems();
if (newItems != null) {
// add all new items to set
for (UUID newItemId : newItems) {
notFinishedItems.put(newItemId, 0L);
}
// add all new items without links to readyItemsList
Map> newLinks = modification.getLinks();
for (UUID newItem : newItems) {
if (newLinks == null) {
logger.debug("apply() new item [{}] has no links and added to readyItemsList [{}]", newItem, readyItemsList);
readyItemsList.add(newItem);
continue;
}
Set itemDependencies = newLinks.get(newItem);
if (itemDependencies == null) {
logger.debug("apply() new item [{}] has no links and added to readyItemsList [{}]", newItem, readyItemsList);
readyItemsList.add(newItem);
continue;
}
// may be some promises already resolved?
boolean isReady = true;
for (UUID promiseItem : itemDependencies) {
if (newItems.contains(promiseItem) || notFinishedItems.containsKey(promiseItem)) {
isReady = false;
break;
}
}
if (isReady) {
readyItemsList.add(newItem);
}
}
}
return readyItemsList;
}
/**
* add all new links to "links" map. update reverseLinks
*
* @return set of items dependent from finished one
*/
private Set updateLinks() {
// update reverse map with new links
Map> reverseLinks = reverseIt(links);
Map> newLinks = modification.getLinks();
if (newLinks != null) {
for (Map.Entry> entry : newLinks.entrySet()) {
Set newItemLinks = entry.getValue();
for (UUID newItemLink : newItemLinks) {
// prevent link to already finished item.
// it is possible case for @NoWait Promise which are used on deep child task
if (!notFinishedItems.containsKey(newItemLink)) {
continue;
}
Set itemLinks = links.get(entry.getKey());
if (itemLinks == null) {
itemLinks = new HashSet<>();
links.put(entry.getKey(), itemLinks);
}
itemLinks.add(newItemLink);
}
// update reverse map
for (UUID thatItem : newItemLinks) {
Set reverseItemLinks = getOrCreateReverseItemLinks(reverseLinks, thatItem);
reverseItemLinks.add(entry.getKey());
}
}
}
return reverseLinks.get(modification.getCompletedItem());
}
/**
* remove finished item from all set.
* find items without dependencies.
*
* @param readyItemsList - collection for ready items found
* @param reverseItemLinks - collection of release candidates
*/
private void findReadyItems(List readyItemsList, Set reverseItemLinks) {
if (reverseItemLinks == null) {
return;
}
UUID finishedItem = modification.getCompletedItem();
for (UUID releaseCandidate : reverseItemLinks) {
Set candidateLinks = links.get(releaseCandidate);
candidateLinks.remove(finishedItem);
UUID dependencySubstitution = modification.getWaitForAfterRelease();
// update changed dependency
if (dependencySubstitution != null) {
candidateLinks.add(dependencySubstitution);
} else {
if (candidateLinks.isEmpty()) {
// GC items without dependencies
links.remove(releaseCandidate);
logger.debug("apply() after remove [{}], item [{}] has no dependencies and added to" +
" readyItemsList [{}]", finishedItem, releaseCandidate, readyItemsList);
readyItemsList.add(releaseCandidate);
}
}
}
}
private static Set getOrCreateReverseItemLinks(Map> reverseLinks, UUID item) {
Set reverseItemLinks = reverseLinks.get(item);
if (reverseItemLinks != null) {
return reverseItemLinks;
}
reverseItemLinks = new HashSet<>();
reverseLinks.put(item, reverseItemLinks);
return reverseItemLinks;
}
public boolean isTaskWaitOtherTasks(UUID taskId, int taskQuantity) {
Set waitForTasks = links.get(taskId);
logger.debug("waitForTasks = " + waitForTasks);
if (waitForTasks == null) {
return false;
}
//noinspection SimplifiableIfStatement
if (taskQuantity == -1 && !waitForTasks.isEmpty()) {
return true;
}
return waitForTasks.size() == taskQuantity;
}
public void clearFinishedItems() {
finishedItems.clear();
}
/**
* Method for creating copy of the graph
*
* @return copy of the current graph
*/
public Graph copy() {
final Graph copy = new Graph();
copy.setGraphId(graphId);
copy.setLinks(copyMapUuidWithSetOfUuid(links));
copy.setNotFinishedItems(new HashMap<>(notFinishedItems));
copy.setFinishedItems(new HashSet<>(finishedItems));
copy.setVersion(version);
copy.setTouchTimeMillis(touchTimeMillis);
copy.setLastApplyTimeMillis(lastApplyTimeMillis);
return copy;
}
private static Map> copyMapUuidWithSetOfUuid(Map> original) {
final Map> copy = new HashMap<>();
for (Map.Entry> entry : original.entrySet()) {
Set copyOfSet = new HashSet<>(entry.getValue());
copy.put(entry.getKey(), copyOfSet);
}
return copy;
}
@Override
public String toString() {
return "Graph{" +
"version=" + version +
", graphId=" + graphId +
", notFinishedItems=" + notFinishedItems +
", links=" + links +
", finishedItems=" + finishedItems +
", modification=" + modification +
", readyItems=" + Arrays.toString(readyItems) +
", touchTimeMillis=" + touchTimeMillis +
", lastApplyTimeMillis=" + lastApplyTimeMillis +
'}';
}
public Collection getProcessTasks() {
Collection allProcessTasks = new HashSet<>();
allProcessTasks.addAll(notFinishedItems.keySet());
allProcessTasks.addAll(finishedItems);
return allProcessTasks;
}
public long getTouchTimeMillis() {
return touchTimeMillis;
}
public void setTouchTimeMillis(long touchTimeMillis) {
this.touchTimeMillis = touchTimeMillis;
}
public long getLastApplyTimeMillis() {
return lastApplyTimeMillis;
}
public void setLastApplyTimeMillis(long lastApplyTimeMillis) {
this.lastApplyTimeMillis = lastApplyTimeMillis;
}
@SuppressWarnings("RedundantIfStatement")
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Graph)) return false;
Graph graph = (Graph) o;
if (lastApplyTimeMillis != graph.lastApplyTimeMillis) return false;
if (touchTimeMillis != graph.touchTimeMillis) return false;
if (version != graph.version) return false;
if (finishedItems != null ? !finishedItems.equals(graph.finishedItems) : graph.finishedItems != null) {
return false;
}
if (graphId != null ? !graphId.equals(graph.graphId) : graph.graphId != null) return false;
if (links != null ? !links.equals(graph.links) : graph.links != null) return false;
if (notFinishedItems != null ? !notFinishedItems.equals(graph.notFinishedItems) : graph.notFinishedItems != null) {
return false;
}
return true;
}
@Override
public int hashCode() {
int result = version;
result = 31 * result + (graphId != null ? graphId.hashCode() : 0);
result = 31 * result + (notFinishedItems != null ? notFinishedItems.hashCode() : 0);
result = 31 * result + (links != null ? links.hashCode() : 0);
result = 31 * result + (finishedItems != null ? finishedItems.hashCode() : 0);
result = 31 * result + (int) (touchTimeMillis ^ (touchTimeMillis >>> 32));
result = 31 * result + (int) (lastApplyTimeMillis ^ (lastApplyTimeMillis >>> 32));
return result;
}
public void removeModification() {
modification = null;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy