Skip to content

Commit

Permalink
CONDEC-618: Speed-up creation of Treant (#215)
Browse files Browse the repository at this point in the history
* Replace getAdjacentElementsAndLink method with inherited graph.getElementsOf(Node node) method since it is faster and easier to understand

* Remove getAdjacentElementsAndLinks method since this method is too hard to understand

* Remove default constructor from Treant class since it is only used for testing

* Split createNodeStructure method of Treant class into smaller private methods
  • Loading branch information
kleebaum authored Oct 23, 2019
1 parent 85e85f6 commit 38d126e
Show file tree
Hide file tree
Showing 6 changed files with 118 additions and 109 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,26 @@ static KnowledgeGraph getOrCreate(String projectKey) {
return knowledgeGraph;
}

Map<DecisionKnowledgeElement, Link> getAdjacentElementsAndLinks(DecisionKnowledgeElement element);

void addEdge(Link link);
/**
* Adds the specified edge ({@link Link} object) to this graph, going from the
* source vertex to the target vertex. More formally, adds the specified edge,
* <code>e</code>, to this graph if this graph contains no edge <code>e2</code>
* such that <code>e2.equals(e)</code>. If this graph already contains such an
* edge, the call leaves this graph unchanged and returns <tt>false</tt>. Some
* graphs do not allow edge-multiplicity. In such cases, if the graph already
* contains an edge from the specified source to the specified target, than this
* method does not change the graph and returns <code>
* false</code>. If the edge was added to the graph, returns <code>
* true</code>.
*
* @param link
* edge to be added to this graph as a {@link Link} object.
*
* @return <tt>true</tt> if this graph did not already contain the specified
* edge.
*
* @see #addEdge(Object, Object)
* @see #getEdgeSupplier()
*/
boolean addEdge(Link link);
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
package de.uhd.ifi.se.decision.management.jira.model.impl;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;

import org.jgrapht.graph.DirectedWeightedMultigraph;
import org.jgrapht.traverse.BreadthFirstIterator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import de.uhd.ifi.se.decision.management.jira.model.DecisionKnowledgeElement;
import de.uhd.ifi.se.decision.management.jira.model.DecisionKnowledgeProject;
Expand All @@ -16,8 +15,6 @@
import de.uhd.ifi.se.decision.management.jira.model.Node;
import de.uhd.ifi.se.decision.management.jira.persistence.KnowledgePersistenceManager;
import de.uhd.ifi.se.decision.management.jira.persistence.impl.AbstractPersistenceManagerForSingleLocation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class KnowledgeGraphImpl extends DirectedWeightedMultigraph<Node, Link> implements KnowledgeGraph {

Expand All @@ -34,23 +31,6 @@ public KnowledgeGraphImpl(String projectKey) {
createGraph();
}

@Override
public Map<DecisionKnowledgeElement, Link> getAdjacentElementsAndLinks(DecisionKnowledgeElement element) {
BreadthFirstIterator<Node, Link> iterator = new BreadthFirstIterator<>(this, element);
// Use First element as parent node
Node parentNode = iterator.next();
Map<DecisionKnowledgeElement, Link> childrenAndLinks = new HashMap<>();
while (iterator.hasNext()) {
Node iterNode = iterator.next();
if (iterator.getParent(iterNode).getId() == parentNode.getId()
&& iterNode instanceof DecisionKnowledgeElement) {
DecisionKnowledgeElement nodeElement = (DecisionKnowledgeElement) iterNode;
childrenAndLinks.put(nodeElement, this.getEdge(parentNode, iterNode));
}
}
return childrenAndLinks;
}

private void createGraph() {
addNodes();
addEdges();
Expand Down Expand Up @@ -90,7 +70,8 @@ private void addEdges() {
}

@Override
public void addEdge(Link link) {
public boolean addEdge(Link link) {
boolean isEdgeCreated = false;
DecisionKnowledgeElement source = link.getSourceElement();
if (!containsVertex(source)) {
addVertex(source);
Expand All @@ -100,9 +81,10 @@ public void addEdge(Link link) {
addVertex(destination);
}
try {
this.addEdge(source, destination, link);
isEdgeCreated = this.addEdge(source, destination, link);
} catch (IllegalArgumentException e) {
LOGGER.error("Error adding link to the graph: " + e.getMessage());
}
return isEdgeCreated;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import java.util.List;
import java.util.Map;

import de.uhd.ifi.se.decision.management.jira.model.*;
import org.jgrapht.traverse.BreadthFirstIterator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand All @@ -26,12 +26,17 @@
import de.uhd.ifi.se.decision.management.jira.config.JiraIssueTypeGenerator;
import de.uhd.ifi.se.decision.management.jira.extraction.GitClient;
import de.uhd.ifi.se.decision.management.jira.filtering.JiraSearchServiceHelper;
import de.uhd.ifi.se.decision.management.jira.model.DecisionKnowledgeElement;
import de.uhd.ifi.se.decision.management.jira.model.DocumentationLocation;
import de.uhd.ifi.se.decision.management.jira.model.KnowledgeGraph;
import de.uhd.ifi.se.decision.management.jira.model.KnowledgeType;
import de.uhd.ifi.se.decision.management.jira.model.Link;
import de.uhd.ifi.se.decision.management.jira.model.Node;
import de.uhd.ifi.se.decision.management.jira.model.impl.DecisionKnowledgeElementImpl;
import de.uhd.ifi.se.decision.management.jira.model.text.PartOfJiraIssueText;
import de.uhd.ifi.se.decision.management.jira.persistence.KnowledgePersistenceManager;
import de.uhd.ifi.se.decision.management.jira.persistence.impl.GenericLinkManager;
import de.uhd.ifi.se.decision.management.jira.persistence.impl.JiraIssueTextPersistenceManager;
import de.uhd.ifi.se.decision.management.jira.view.treant.TreantNode;

public class CommonMetricCalculator {

Expand All @@ -40,7 +45,6 @@ public class CommonMetricCalculator {
private JiraIssueTextPersistenceManager persistenceManager;
private String jiraIssueTypeId;
private List<Issue> jiraIssues;
private int absolutDepth;
private final String dataStringSeparator = " ";

private static final Logger LOGGER = LoggerFactory.getLogger(CommonMetricCalculator.class);
Expand Down Expand Up @@ -274,14 +278,33 @@ public Map<String, Integer> getLinkDistance(KnowledgeType type) {
List<DecisionKnowledgeElement> listOfDecKnowElements = persistenceManager.getDecisionKnowledgeElements(type);
Integer i = 0;
for (DecisionKnowledgeElement currentElement : listOfDecKnowElements) {
int depth = graphRecursionBot(currentElement);
int depth = getLinkDistanceFromSingleNode(currentElement);
i++;
linkDistances.put(String.valueOf(i) + currentElement.getKey(), depth);
}

return linkDistances;
}

private int getLinkDistanceFromSingleNode(DecisionKnowledgeElement dke) {
int absolutDepth = 0;
KnowledgeGraph graph = KnowledgeGraph.getOrCreate(projectKey);
BreadthFirstIterator<Node, Link> iterator = new BreadthFirstIterator<>(graph, dke);
Node parentNode = iterator.next();
int currentDepth = 0;
while (iterator.hasNext()) {
Node iterNode = iterator.next();
++currentDepth;
if (iterator.getParent(iterNode).equals(parentNode)) {
currentDepth = 1;
}
if (absolutDepth < currentDepth) {
absolutDepth = currentDepth;
}
}
return absolutDepth;
}

public Map<String, String> getLinksToIssueTypeMap(KnowledgeType knowledgeType) {
if (knowledgeType == null) {
return null;
Expand Down Expand Up @@ -328,37 +351,4 @@ private boolean checkEqualIssueTypeIssue(IssueType issueType2) {
String jiraIssueTypeName = JiraIssueTypeGenerator.getJiraIssueTypeName(jiraIssueTypeId);
return issueType2.getName().equalsIgnoreCase(jiraIssueTypeName);
}

private int graphRecursionBot(DecisionKnowledgeElement dke) {
this.absolutDepth = 0;
KnowledgeGraph graph = KnowledgeGraph.getOrCreate(projectKey);
this.createNodeStructure(dke, null, 100, 1, graph);
return absolutDepth;
}

private TreantNode createNodeStructure(DecisionKnowledgeElement element, Link link, int depth, int currentDepth,
KnowledgeGraph graph) {
if (element == null || element.getProject() == null || element.getType() == KnowledgeType.OTHER) {
return new TreantNode();
}
Map<DecisionKnowledgeElement, Link> childrenAndLinks = graph.getAdjacentElementsAndLinks(element);
TreantNode node;
if (link != null) {
node = new TreantNode(element, link, false, false);
} else {
node = new TreantNode(element, false, false);
}
List<TreantNode> nodes = new ArrayList<TreantNode>();
for (Map.Entry<DecisionKnowledgeElement, Link> childAndLink : childrenAndLinks.entrySet()) {
TreantNode newChildNode = createNodeStructure(childAndLink.getKey(), childAndLink.getValue(), depth,
currentDepth + 1, graph);
if (absolutDepth < currentDepth) {
absolutDepth = currentDepth;
}
nodes.add(newChildNode);
}
node.setChildren(nodes);
return node;
}

}
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package de.uhd.ifi.se.decision.management.jira.view.treant;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
Expand All @@ -11,17 +12,17 @@

import com.atlassian.jira.user.ApplicationUser;

import de.uhd.ifi.se.decision.management.jira.filtering.FilterExtractor;
import de.uhd.ifi.se.decision.management.jira.filtering.impl.FilterExtractorImpl;
import de.uhd.ifi.se.decision.management.jira.model.DecisionKnowledgeElement;
import de.uhd.ifi.se.decision.management.jira.model.DocumentationLocation;
import de.uhd.ifi.se.decision.management.jira.model.KnowledgeGraph;
import de.uhd.ifi.se.decision.management.jira.model.KnowledgeType;
import de.uhd.ifi.se.decision.management.jira.model.Link;
import de.uhd.ifi.se.decision.management.jira.persistence.KnowledgePersistenceManager;
import de.uhd.ifi.se.decision.management.jira.persistence.impl.AbstractPersistenceManagerForSingleLocation;

/**
* Creates Treant content
* Creates a tree data structure from the {@link KnowledgeGraph}. Uses the
* Treant.js framework for visualization of the knowledge tree.
*/
@XmlRootElement(name = "treant")
@XmlAccessorType(XmlAccessType.FIELD)
Expand All @@ -34,9 +35,8 @@ public class Treant {

private KnowledgeGraph graph;
private boolean isHyperlinked;

public Treant() {
}
private Set<Link> traversedLinks;
private int depth;

public Treant(String projectKey, String elementKey, int depth, boolean isHyperlinked) {
this(projectKey, elementKey, depth, null, null, isHyperlinked);
Expand All @@ -46,13 +46,19 @@ public Treant(String projectKey, String elementKey, int depth) {
this(projectKey, elementKey, depth, false);
}

public Treant(String projectKey, String elementKey, int depth, String query, ApplicationUser user) {
this(projectKey, elementKey, depth, query, user, false);
}

public Treant(String projectKey, String elementKey, int depth, String query, ApplicationUser user,
boolean isHyperlinked) {
FilterExtractor filterExtractor = new FilterExtractorImpl(projectKey, user, query);
filterExtractor.getAllElementsMatchingQuery();
this.traversedLinks = new HashSet<Link>();
this.depth = depth;
this.graph = KnowledgeGraph.getOrCreate(projectKey);

AbstractPersistenceManagerForSingleLocation persistenceManager;
// TODO pass entire element object to the constructors instead of only the key
// and use the documentation location of the element
if (elementKey.contains(":")) {
persistenceManager = KnowledgePersistenceManager.getOrCreate(projectKey)
.getPersistenceManager(DocumentationLocation.JIRAISSUETEXT);
Expand All @@ -61,48 +67,61 @@ public Treant(String projectKey, String elementKey, int depth, String query, App
}
DecisionKnowledgeElement rootElement = persistenceManager.getDecisionKnowledgeElement(elementKey);
this.setChart(new Chart());
this.setNodeStructure(this.createNodeStructure(rootElement, null, depth, 1));
this.setNodeStructure(this.createNodeStructure(rootElement, null, 1));
this.setHyperlinked(isHyperlinked);
}

public Treant(String projectKey, String elementKey, int depth, String query, ApplicationUser user) {
this(projectKey, elementKey, depth, query, user, false);
}

public TreantNode createNodeStructure(DecisionKnowledgeElement element, Link link, int depth, int currentDepth) {
public TreantNode createNodeStructure(DecisionKnowledgeElement element, Link link, int currentDepth) {
if (element == null || element.getProject() == null) {
return new TreantNode();
}
Set<Link> linksToTraverse = graph.edgesOf(element);
boolean isCollapsed = isNodeCollapsed(linksToTraverse, currentDepth);
TreantNode node = createTreantNode(element, link, isCollapsed);

if (graph == null) {
graph = KnowledgeGraph.getOrCreate(element.getProject().getProjectKey());
if (currentDepth == depth + 1) {
return node;
}
Map<DecisionKnowledgeElement, Link> childrenAndLinks = graph.getAdjacentElementsAndLinks(element);

List<TreantNode> nodes = getChildren(element, linksToTraverse, currentDepth);
node.setChildren(nodes);

return node;
}

private boolean isNodeCollapsed(Set<Link> linksToTraverse, int currentDepth) {
boolean isCollapsed = false;
if (currentDepth == depth && childrenAndLinks.size() != 0) {
if (currentDepth == depth && !traversedLinks.containsAll(linksToTraverse)) {
isCollapsed = true;
}
return isCollapsed;
}

private TreantNode createTreantNode(DecisionKnowledgeElement element, Link link, boolean isCollapsed) {
TreantNode node;
if (link != null) {
node = new TreantNode(element, link, isCollapsed, isHyperlinked);
} else {
node = new TreantNode(element, isCollapsed, isHyperlinked);
}
return node;
}

if (currentDepth == depth + 1) {
return node;
}

private List<TreantNode> getChildren(DecisionKnowledgeElement rootElement, Set<Link> linksToTraverse,
int currentDepth) {
List<TreantNode> nodes = new ArrayList<TreantNode>();
for (Map.Entry<DecisionKnowledgeElement, Link> childAndLink : childrenAndLinks.entrySet()) {
TreantNode newChildNode = createNodeStructure(childAndLink.getKey(), childAndLink.getValue(), depth,
currentDepth + 1);
for (Link currentLink : linksToTraverse) {
if (!traversedLinks.add(currentLink)) {
continue;
}
DecisionKnowledgeElement oppositeElement = currentLink.getOppositeElement(rootElement);
if (oppositeElement.getType() == KnowledgeType.OTHER) {
continue;
}
TreantNode newChildNode = createNodeStructure(oppositeElement, currentLink, currentDepth + 1);
nodes.add(newChildNode);
}
node.setChildren(nodes);

return node;
return nodes;
}

public Chart getChart() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,7 @@ public TreantNode(DecisionKnowledgeElement decisionKnowledgeElement, boolean isC
this.htmlId = decisionKnowledgeElement.getId();
DecisionKnowledgeProject project = decisionKnowledgeElement.getProject();
this.link = new HashMap<String, String>();
if (decisionKnowledgeElement.getDescription() != null
&& !decisionKnowledgeElement.getDescription().equals("")) {
if (decisionKnowledgeElement.getDescription() != null && !decisionKnowledgeElement.getDescription().isBlank()) {
this.link.put("title", decisionKnowledgeElement.getDescription());
}
if (project.isIssueStrategy() && isHyperlinked) {
Expand Down
Loading

0 comments on commit 38d126e

Please sign in to comment.