/*
* Created on Oct 20, 2005
*
*/
package org.incha.core.jswingripples.eig;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.core.resources.IProject;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IMember;
import org.eclipse.jdt.core.IType;
import org.incha.core.JavaProject;
import org.incha.core.jswingripples.eig.history.History;
/**
* <p>A software system can be viewed as a set of components and their interoperations and
* can be formally modeled as graph. In a nutshell, in this
* graph components are represented as nodes and interactions are represented as edges
* among the nodes. As the software evolves and changes, its components change too.
* Moreover, a change, introduced in a one component, may propagate through the
* interoperation dependencies to other components as well. To reflect the change
* process in the graph, we use marks.</p>
* <p>The described graph, which contains components, their interoperations, marks and
* has a set of propagation rules defined, is called <b>Evolving Interoperation Graph (EIG)</b>.</p>
* <p>JRipples EIG is created by
* <ol>
* <li>setting up a project under analysis,
* <li>setting up a main class to an analysis from,
* <li>adding IMember Java Elements of the project under analysis to wrapped with JRipples EIG nodes
* <li>adding edges between created nodes
* </ol>
* </p>
* <p>There is only one instance of JRipplesEIG is created during JRipples plug-in
* lifecycle and thus only one project can be analyzed in the same time. Consequently, most of the
* JRipplesEIG methods are static and refer to the same dependency graph.</p>
* <p>Even though storing in the same way, JRipples EIG classifies every node as a top node or
* a member node based on whether underlying IMember object is a top in a class nesting hierarchy.</p>
* <p>
* Here is a short example on how JRipples EIG is typically used:
* <pre>
* <code>
*
* JRipplesEIG.initEIG();
*
* JRipplesEIG.setProject((IProject) someProject);
* JRipplesEIG.setMainClass((IType) someType);
*
*JRipplesEIG.doLock(this);
* JRipplesEIGNode node1=JRippelsEIG.addNode((IMember) member1);
* JRipplesEIGNode node2=JRippelsEIG.addNode((IMember) member2);
* JRipplesEIGNode node3=JRippelsEIG.addNode((IMember) member3);
*
* JRippelsEIGEdge edge1=JRippelsEIG.addEdge(node1, node2);
* JRippelsEIGEdge edge1=JRippelsEIG.addEdge(node1, JRipplesEIG.getNode((IMember)member2));
* JRippelsEIGEdge edge1=JRippelsEIG.addEdge(JRipplesEIG.getNode((IMember)member3), JRipplesEIG.getNode((IMember)member2));
* JRipplesEIG.doUnLock(this); //All the EIG listeners will be notified of the changes
*
* ...some logic...
*
* JRipplesEIG.doLock(this);
*
* node1.setMark("Changed");
* JRipplesEIG.removeNode(node2);
* node3.setMark("Visited");
*
* JRipplesEIG.doUnLock(this); //All the EIG listeners will be notified of the changes
*
* node3.setProbability("12"); //All the EIG listeners will be notified of the change
*
* </code>
* </pre>
* </p>
*
* @author Maksym Petrenko
* @see #initEIG()
* @see #setProject(IProject)
* @see #setMainClass(IType)
* @see #addNode(IMember)
* @see #addEdge(JSwingRipplesEIGNode, JSwingRipplesEIGNode)
* @see JSwingRipplesEIGNode
* @see JSwingRipplesEIGEdge
*
*/
public final class JSwingRipplesEIG {
private Map<IMember, JSwingRipplesEIGNode> nodes;
private Set<JSwingRipplesEIGNode> topNodes;
private Map<JSwingRipplesEIGNode, HashSet<JSwingRipplesEIGNode>> members; //Node -> Set of members
private Collection<JSwingRipplesEIGEdge> edges;
private Map<JSwingRipplesEIGNode, HashMap<JSwingRipplesEIGNode, JSwingRipplesEIGEdge>> edgesAdjacency; //Node -> Set of related nodes
private Map<JSwingRipplesEIGNode, JSwingRipplesEIGEdge> nodesAdjacency; //Node and its direct related edges, find missing edges
private String mainClass;
private Set<JSwingRipplesEIGListener> eigListeners = Collections.synchronizedSet(new HashSet<JSwingRipplesEIGListener>());
private final JavaProject projectName;
boolean redoInProgress = false;
protected static final int UNDOABLE = 1;
protected static final int REDOABLE = 2;
protected static final int NONEABLE = 4;
private final History history = new History();
// ------------------------------- EIG General ------------------------------
public JSwingRipplesEIG(final JavaProject projectName) {
initEIG();
this.projectName = projectName;
}
/**
* Reinitializes JRipples EIG for a new analysis - that is, it deletes all nodes and edges from the database and clears undo / redo history.
* <br>Please note, that project and main class are not reset.
*/
public void initEIG() {
if (edges!=null)
synchronized (edges) {
for (final Iterator<JSwingRipplesEIGEdge> iter=edges.iterator();iter.hasNext();) {
fireJRipplesEIGChanged(iter.next(), JSwingRipplesEIGEdgeEvent.EDGE_REMOVED);
}
}
if (nodes!=null)
synchronized (nodes) {
for (final Iterator<JSwingRipplesEIGNode> iter=nodes.values().iterator();iter.hasNext();) {
fireJRipplesEIGChanged(iter.next(),
JSwingRipplesEIGNodeEvent.NODE_REMOVED, null, null);
}
}
/*
if (nodes!=null)
if (nodes.values()!=null)
batchRemoveNodes( new LinkedHashSet (nodes.values()));
*/
if (nodes!=null) {
final JSwingRipplesEIGNode []nodesArr=getAllNodes();
nodes.clear();
for (int i=0;i<nodesArr.length;i++) {
nodesArr[i]=null;
}
}
if (edges!=null) {
final JSwingRipplesEIGEdge []edgesArr=getAllEdges();
edges.clear();
for (int i=0;i<edgesArr.length;i++) {
edgesArr[i]=null;
}
}
if (nodes == null)
nodes = Collections.synchronizedMap(new HashMap<IMember, JSwingRipplesEIGNode>());
if (topNodes!=null) topNodes.clear();
else topNodes = Collections.synchronizedSet(new HashSet<JSwingRipplesEIGNode>());
if (members!=null) members.clear();
else members=Collections.synchronizedMap(new HashMap<JSwingRipplesEIGNode, HashSet<JSwingRipplesEIGNode>>());
if (edges == null)
edges = Collections.synchronizedSet(new HashSet<JSwingRipplesEIGEdge>());
if (edgesAdjacency!=null) edgesAdjacency.clear();
else edgesAdjacency = Collections.synchronizedMap(new HashMap<JSwingRipplesEIGNode, HashMap<JSwingRipplesEIGNode, JSwingRipplesEIGEdge>>());
//keep step with edgesAdjacency
if (nodesAdjacency!=null) nodesAdjacency.clear();
else nodesAdjacency = Collections.synchronizedMap(new HashMap<JSwingRipplesEIGNode, JSwingRipplesEIGEdge>());
}
/**
* Checks whether the JRipples EIG is inialized for analysis - that is, whether a main project and main class are set, and whether EIG has at least one node registered with it.
* @return
* <code>true</code> if the JRipples EIG is inialized for analysis, <br><code>null</code> otherwise
* @see #getProject()
* @see #getMainClass()
* @see #getAllNodes()
*/
public boolean isInitialized() {
if (getMainClass() == null) {
return false;
}
if (nodes == null) {
return false;
}
;
if (nodes.size() == 0) {
return false;
}
return true;
}
/**
* Sets a main class (a class to start the analysis from) of a project under analysis, usually chosen through "JRipples > Start" menu.
* @param type
* a main class of a project this JRipples EIG analysis reffers to
* @see #getProject()
* @see #setProject(IProject)
* @see #getMainClass()
*
*/
public void setMainClass(final String type) {
mainClass = type;
}
/**
* Returns the main class (a class to start the analysis from) of a project under analysis, usually chosen through "JRipples > Start" menu.
* @return
* a main class of a project this JRipples EIG analysis reffers to
* @see #getProject()
* @see #setProject(IProject)
* @see #setMainClass(IType)
*/
public String getMainClass() {
return mainClass;
}
// ------------------------------------- EIG Node operations -------------------------
/**
* Creates and adds to the JRipples EIG a node that wraps a supplied IMember object
* @return
* an existing node if one found in JRipples EIG,<br>
* a created node if there was no such node declared before,<br>
* <code>null</code> if the supplied IMember object is <code>null</code>
*/
public JSwingRipplesEIGNode addNode(final IMember nodeIMember) {
if (nodeIMember==null) return null;
if (nodes.containsKey(nodeIMember)) return nodes.get(nodeIMember);
final JSwingRipplesEIGNode node=new JSwingRipplesEIGNode(this, nodeIMember);
if (!members.keySet().contains(node)) {
final HashSet<JSwingRipplesEIGNode> nodeMembers=new HashSet<JSwingRipplesEIGNode>();
members.put(node,nodeMembers);
}
try {
if (node.isTop()) {
topNodes.add(node);
} else {
JSwingRipplesEIGNode parentNode=findParentNodeForMemberNode(node);
if (parentNode==null) parentNode=addNode(JSwingRipplesIMemberServices.getMemberParent(nodeIMember));
if (parentNode!=null) {
members.get(parentNode).add(node);
}
}
} catch (final Exception e) {
//do nothing
}
nodes.put(nodeIMember, node);
fireJRipplesEIGChanged(node, JSwingRipplesEIGNodeEvent.NODE_ADDED, null, null);
return node;
}
/**
* Removes a node from the JRipples EIG. It also removes all edges,
* associated with this node. If the node is top, the same set of
* actions will be applied to all member node of this node as well -
* the member nodes together with their associated edges will
* be removed from the JRipples EIG.
* @param node
* node to remove
* @see #getNodeMembers(JSwingRipplesEIGNode)
* @see JSwingRipplesEIGNode#isTop()
* @see #removeEdge(JSwingRipplesEIGEdge)
*/
public void removeNode(final JSwingRipplesEIGNode node) {
//Remove members if any
LinkedHashSet<JSwingRipplesEIGNode> nodesToBeRemoved=new LinkedHashSet<JSwingRipplesEIGNode>();
nodesToBeRemoved.add(node);
if (node.isTop()) topNodes.remove(node);
walkMembers(node,nodesToBeRemoved);
batchRemoveNodes(nodesToBeRemoved); //Remove encountered nodes and edges
nodesToBeRemoved.clear();
nodesToBeRemoved=null;
}
/**
* Walks through the members of the node and adds them to the list of the nodes to be removed in the batch
* @param node
* @param nodesToBeRemoved
*/
private void walkMembers(final JSwingRipplesEIGNode node, final LinkedHashSet<JSwingRipplesEIGNode> nodesToBeRemoved) {
HashSet<JSwingRipplesEIGNode> membersSet=members.get(node);
if (membersSet.size()>0) {
for (final Iterator <JSwingRipplesEIGNode>iter = membersSet.iterator();iter.hasNext();) {
final JSwingRipplesEIGNode nodeTmp=iter.next();
nodesToBeRemoved.add(nodeTmp);
walkMembers(nodeTmp, nodesToBeRemoved);
}
membersSet.clear();
members.remove(node);
membersSet=null;
}
}
/**
* Removes nodes and their edges in a batch
* @param nodesToBeRemoved
*/
private void batchRemoveNodes(final LinkedHashSet<JSwingRipplesEIGNode> nodesToBeRemoved) {
if (nodesToBeRemoved==null) return;
if (nodesToBeRemoved.size()==0) return;
Collection<JSwingRipplesEIGEdge> tmpEdges = new HashSet<JSwingRipplesEIGEdge>();
synchronized (edges) {
for (final Iterator<JSwingRipplesEIGEdge> iter = edges.iterator(); iter.hasNext();) {
final JSwingRipplesEIGEdge edge = iter.next();
if ((edge.getFromNode() != null) && ((edge.getToNode() != null)))
if ((nodesToBeRemoved.contains(edge.getFromNode()))
|| (nodesToBeRemoved.contains(edge.getToNode())))
tmpEdges.add(edge);
}
}
if (tmpEdges.size() > 0) {
for (final Iterator<JSwingRipplesEIGEdge> iter=tmpEdges.iterator();iter.hasNext();) {
removeEdge(iter.next());
}
tmpEdges.clear();
}
tmpEdges=null;
for (final Iterator<JSwingRipplesEIGNode> iter=nodesToBeRemoved.iterator();iter.hasNext();) {
JSwingRipplesEIGNode nodeTmp=iter.next();
nodes.remove(nodeTmp.getNodeIMember());
fireJRipplesEIGChanged(nodeTmp, JSwingRipplesEIGNodeEvent.NODE_REMOVED, null, null);
nodeTmp = null;
}
}
/**
* Checks whether JRipples EIG database contains a node with provided
* IMember object.
* @param nodeMember
* underlying IMember object of a node to find
* @return
* <code>true</code> if JRipples EIG database contains a node with provided
* IMember object, <br> <code>false</code> otherwise
* @see JSwingRipplesEIGNode#getNodeIMember()
* @see #getNode(IMember)
*
*/
public boolean existsNode(final IMember nodeMember) {
if (nodeMember==null) return false;
return nodes.keySet().contains(nodeMember);
}
/**
* Checks whether JRipples EIG database contains a node with provided
* IMember object and returns it if any.
* @param nodeMember
* underlying IMember object of a node to return
* @return
* a node with supplied underlying IMember object if JRipples EIG database contains such a node
* , <br> <code>null</code> otherwise
* @see #existsNode(IMember)
* @see JSwingRipplesEIGNode#getNodeIMember()
*/
public JSwingRipplesEIGNode getNode(final IMember nodeMember) {
if (nodeMember==null) return null;
if (!existsNode(nodeMember)) return null;
return nodes.get(nodeMember);
}
/**
* Returns all the nodes, registered with this EIG (that is, created with {@link #addNode(IMember)} method).
* @return
* all the nodes, registered with this EIG (that is, created with {@link #addNode(IMember)} method),<br> or empty array if no node was found.
*/
public JSwingRipplesEIGNode[] getAllNodes() {
if (nodes == null) return new JSwingRipplesEIGNode[0];
final Collection<JSwingRipplesEIGNode> values = nodes.values();
return values.toArray(new JSwingRipplesEIGNode[values.size()]);
}
/**
* Returns all the top nodes (nodes, whose underlying IMember object is top class hierarchy), registered with this EIG.
* @return
* all the top nodes, registered with this EIG, <br> or empty array if no top node was found.
*/
public JSwingRipplesEIGNode[] getTopNodes() {
if (topNodes == null) return new JSwingRipplesEIGNode[0];
return topNodes.toArray(new JSwingRipplesEIGNode[topNodes.size()]);
}
//===============Member's operations ============================================
/**
* Returns member nodes, whose underlying IMember Java elements are defined within IMember Java element of a supplied top node.
* @param node
* node, whose member nodes should be returned
* @return
* nodes, whose underlying IMember Java elements are defined within IMember Java element of a supplied top node, if any, <br>
* empty array otherwise
* @see JSwingRipplesEIGNode#isTop()
* @see #getTopNodes()
* @see #findTopNodeForMemberNode(JSwingRipplesEIGNode)
*/
public JSwingRipplesEIGNode[] getNodeMembers(final JSwingRipplesEIGNode node) {
if (node==null) return new JSwingRipplesEIGNode[0];
if (!members.keySet().contains(node)) return new JSwingRipplesEIGNode[0];
final HashSet<JSwingRipplesEIGNode> membersSet=members.get(node);
return membersSet.toArray(new JSwingRipplesEIGNode[membersSet.size()]);
}
/**
* Finds a top node, whose underlying IMember object declares the supplied member parameter.
* @param member
* member object, for which a top node should be found
* @return
* a node, if any, whose underlying IMember object declares the supplied member parameter, <br>or <code>null</code> otherwise
* @see #getTopNodes()
* @see #getNodeMembers(JSwingRipplesEIGNode)
* @see #findTopNodeForMemberNode(JSwingRipplesEIGNode)
* @see JSwingRipplesEIGNode#isTop()
*/
public JSwingRipplesEIGNode findTopNodeForIMember(final IMember member) {
final IType declaringType=JSwingRipplesIMemberServices.getTopDeclaringType(member);
if (declaringType==null) return null;
final JSwingRipplesEIGNode topNode=getNode(declaringType);
return topNode;
}
/**
* Finds a parent node, whose underlying IMember object declares the supplied member parameter.
* @param member
* member object, for which a top node should be found
* @return
* a parent node, if any, whose underlying IMember object declares the supplied member parameter, <br>or <code>null</code> otherwise
* @see #getTopNodes()
* @see #getNodeMembers(JSwingRipplesEIGNode)
* @see #findTopNodeForMemberNode(JSwingRipplesEIGNode)
* @see JSwingRipplesEIGNode#isTop()
*/
private JSwingRipplesEIGNode findParentNodeForIMember(final IMember member) {
final IMember parent=JSwingRipplesIMemberServices.getMemberParent(member);
if (parent==null) return null;
final JSwingRipplesEIGNode topNode=getNode(parent);
return topNode;
}
/**
* Finds a top node for a supplied member node.
* @param node
* @return
* the supplied node if it is top,<br>
* a top node, whose underlying IMember Java element declares underlying IMember Java element of the supplied node, <br>
*<code>null</code> otherwise
* @see JSwingRipplesEIGNode#isTop()
* @see #getNodeMembers(JSwingRipplesEIGNode)
* @see #getTopNodes()
*/
public JSwingRipplesEIGNode findTopNodeForMemberNode(final JSwingRipplesEIGNode node) {
//if (node.isTop()) return node;
return findTopNodeForIMember(node.getNodeIMember());
}
/**
* Finds a parent node for a supplied member node.
* @param node
* @return
* the supplied node if it is top,<br>
* a parent node, whose underlying IMember Java element declares underlying IMember Java element of the supplied node, <br>
*<code>null</code> otherwise
* @see JSwingRipplesEIGNode#isTop()
* @see #getNodeMembers(JSwingRipplesEIGNode)
* @see #getTopNodes()
*/
public JSwingRipplesEIGNode findParentNodeForMemberNode(final JSwingRipplesEIGNode node) {
return findParentNodeForIMember(node.getNodeIMember());
}
// -----------------------------------Node Neigbors -----------------------------------------
/**
* direction constant indicating that only calling nodes (nodes, that call the supplied centralNode) should be returned
* <br>to be used with {@link #edgesToNeigbors(Set, int, int)}
*/
public static final int DIRECTION_CONSIDERED_CALLING_NODES_ONLY=-1;
/**
* direction constant indicating that both called and calling nodes should be returned
* <br>to be used with {@link #edgesToNeigbors(Set, int, int)}
*/
public static final int DIRECTION_CONSIDERED_BOTH_CALLING_AND_CALLED=0;
/**
* direction constant indicating that only called nodes (nodes, that are called from the supplied centralNode) should be returned
* <br>to be used with {@link #edgesToNeigbors(Set, int, int)}
*/
public static final int DIRECTION_CONSIDERED_CALLED_NODES_ONLY=1;
/**
* nesting constant indicating that only top nodes should be returned
* <br>to be used with {@link #edgesToNeigbors(Set, int, int)}
*/
public static final int NESTING_CONSIDERED_TOP_NODES_ONLY=-1;
/**
* nesting constant indicating that both top and member nodes should be returned
* <br>to be used with {@link #edgesToNeigbors(Set, int, int)}
*/
public static final int NESTING_CONSIDERED_BOTH_TOP_AND_MEMBER_NODES=0;
/**
* nesting constant indicating that only member nodes should be returned
* <br>to be used with {@link #edgesToNeigbors(Set, int, int)}
*/
public static final int NESTING_CONSIDERED_MEMBER_NODES_ONLY=1;
/**
*Based on supplied parameters, returns a set of JRippelsEIGNodes that contain
*<ul>
*<li>either top nodes, or member nodes, or both
*<li>that
*<li>either call, or are called, or both
*<li>by
*<li>any of the supplied nodes (directly, but not transitively through the nodes's members)
*</ul>
* <br>Direction constants:
* <ul>
* <li>{@link #DIRECTION_CONSIDERED_BOTH_CALLING_AND_CALLED}
* <li>{@link #DIRECTION_CONSIDERED_CALLING_NODES_ONLY}
* <li>{@link #DIRECTION_CONSIDERED_CALLED_NODES_ONLY}
* </ul>
* <br>Nesting constants:
* <ul>
* <li>{@link #NESTING_CONSIDERED_BOTH_TOP_AND_MEMBER_NODES}
* <li>{@link #NESTING_CONSIDERED_TOP_NODES_ONLY}
* <li>{@link #NESTING_CONSIDERED_MEMBER_NODES_ONLY}
* </ul>
*
* @param nodes
* Set of {@link JSwingRipplesEIGNode} nodes, of which direct (not transitive through the node's members) neighbors should be returned
* @param directionConsidered
* whether to return neighbors that only call, are called by, or do both to the supplied nodes
* @param nestingConsidered
* whether to return neighbors that are top nodes, member nodes, or both
* @return
* Set of {@link JSwingRipplesEIGNode} nodes that satisfy specified requirements; set may be empty
* @see #getAllAnyNodeNeighbors(JSwingRipplesEIGNode)
* @see #getAllTopNodeNeighbors(JSwingRipplesEIGNode)
* @see #getAllMemberNodeNeighbors(JSwingRipplesEIGNode)
* @see #getIncomingAnyNodeNeighbors(JSwingRipplesEIGNode)
* @see #getIncomingTopNodeNeighbors(JSwingRipplesEIGNode)
* @see #getIncomingMemberNodeNeighbors(JSwingRipplesEIGNode)
* @see #getOutgoingAnyNodeNeighbors(JSwingRipplesEIGNode)
* @see #getOutgoingTopNodeNeighbors(JSwingRipplesEIGNode)
* @see #getOutgoingMemberNodeNeighbors(JSwingRipplesEIGNode)
*
*
*/
public HashSet<JSwingRipplesEIGNode> edgesToNeigbors(final Set<JSwingRipplesEIGNode> nodes, final int directionConsidered, final int nestingConsidered ) {
//TODO Can be done as bit masks and enums
final HashSet<JSwingRipplesEIGNode> result=new HashSet<JSwingRipplesEIGNode>();
if (nodes==null) return result;
if (nodes.size()==0) return result;
synchronized (edges) {
for (final Iterator<JSwingRipplesEIGEdge> iter = edges.iterator(); iter.hasNext();) {
final JSwingRipplesEIGEdge edge = iter.next();
if ((edge.getFromNode() != null) && ((edge.getToNode() != null))) {
//fromNode->centralNode (centralNode==toNode)
if ((nodes.contains(edge.getToNode()))) {
if ((directionConsidered==DIRECTION_CONSIDERED_CALLING_NODES_ONLY) || (directionConsidered==DIRECTION_CONSIDERED_BOTH_CALLING_AND_CALLED)) {
if (topNodes.contains(edge.getFromNode())) {
if ((nestingConsidered==NESTING_CONSIDERED_TOP_NODES_ONLY) || (nestingConsidered==NESTING_CONSIDERED_BOTH_TOP_AND_MEMBER_NODES))
result.add(edge.getFromNode());
} else {
if ((nestingConsidered==NESTING_CONSIDERED_MEMBER_NODES_ONLY) || (nestingConsidered==NESTING_CONSIDERED_BOTH_TOP_AND_MEMBER_NODES))
result.add(edge.getFromNode());
if ((nestingConsidered==NESTING_CONSIDERED_TOP_NODES_ONLY) || (nestingConsidered==NESTING_CONSIDERED_BOTH_TOP_AND_MEMBER_NODES))
result.add(findTopNodeForMemberNode(edge.getFromNode()));
}
}
}
//centralNode->toNode (centralNode==fromNode)
if ((nodes.contains(edge.getFromNode()))) {
if ((directionConsidered==DIRECTION_CONSIDERED_CALLED_NODES_ONLY) || (directionConsidered==DIRECTION_CONSIDERED_BOTH_CALLING_AND_CALLED)) {
//add missing edges
if(edge.getToNode().getNodeIMember().getElementType()==IJavaElement.FIELD){
final JSwingRipplesEIGEdge parentEdge = nodesAdjacency.get(edge.getToNode());
if(parentEdge!=null)/*this expression can solve when calling an array but that array is not initialized, JRipples halts*/
result.add(parentEdge.getToNode());
}
if (topNodes.contains(edge.getToNode())) {
if ((nestingConsidered==NESTING_CONSIDERED_TOP_NODES_ONLY) || (nestingConsidered==NESTING_CONSIDERED_BOTH_TOP_AND_MEMBER_NODES))
result.add(edge.getToNode());
} else {
if ((nestingConsidered==NESTING_CONSIDERED_MEMBER_NODES_ONLY) || (nestingConsidered==NESTING_CONSIDERED_BOTH_TOP_AND_MEMBER_NODES))
result.add(edge.getToNode());
if ((nestingConsidered==NESTING_CONSIDERED_TOP_NODES_ONLY) || (nestingConsidered==NESTING_CONSIDERED_BOTH_TOP_AND_MEMBER_NODES))
result.add(findTopNodeForMemberNode(edge.getToNode()));
}
}
}
}
}
}
return result;
}
//-----Node-to-TopNode neigbors ----
/**
* Returns top nodes that both call and are called by both the supplied node and it's member nodes if any
* <br>Works by calling {@link #edgesToNeigbors(Set, int, int)} method with predefined parameters
* @param node
* node, whose neighboring nodes should be returned
* @return
* neighboring nodes
*/
public JSwingRipplesEIGNode[] getAllTopNodeNeighbors(
final JSwingRipplesEIGNode node) {
if (node==null) return new JSwingRipplesEIGNode[0];
final HashSet<JSwingRipplesEIGNode> nodes=new HashSet<JSwingRipplesEIGNode>();
nodes.add(node);
if (members.containsKey(node))
nodes.addAll(members.get(node));
final Collection<JSwingRipplesEIGNode> neighbors=edgesToNeigbors(nodes,DIRECTION_CONSIDERED_BOTH_CALLING_AND_CALLED,NESTING_CONSIDERED_TOP_NODES_ONLY);
return neighbors
.toArray(new JSwingRipplesEIGNode[neighbors.size()]);
}
/**
* Returns top nodes that call both the supplied node and it's member nodes if any
* <br>Works by calling {@link #edgesToNeigbors(Set, int, int)} method with predefined parameters
* @param node
* node, whose neighboring nodes should be returned
* @return
* neighboring nodes
*/
public JSwingRipplesEIGNode[] getIncomingTopNodeNeighbors(
final JSwingRipplesEIGNode node) {
if (node==null) return new JSwingRipplesEIGNode[0];
final HashSet<JSwingRipplesEIGNode> nodes=new HashSet<JSwingRipplesEIGNode>();
nodes.add(node);
if (members.containsKey(node))
nodes.addAll(members.get(node));
final Collection<JSwingRipplesEIGNode> neighbors=edgesToNeigbors(nodes,DIRECTION_CONSIDERED_CALLING_NODES_ONLY,NESTING_CONSIDERED_TOP_NODES_ONLY);
return neighbors
.toArray(new JSwingRipplesEIGNode[neighbors.size()]);
}
/**
* Returns top nodes that are called by both the supplied node and it's member nodes if any
* <br>Works by calling {@link #edgesToNeigbors(Set, int, int)} method with predefined parameters
* @param node
* node, whose neighboring nodes should be returned
* @return
* neighboring nodes
*/
public JSwingRipplesEIGNode[] getOutgoingTopNodeNeighbors(
final JSwingRipplesEIGNode node) {
if (node==null) return new JSwingRipplesEIGNode[0];
final HashSet<JSwingRipplesEIGNode> nodes=new HashSet<JSwingRipplesEIGNode>();
nodes.add(node);
if (members.containsKey(node))
nodes.addAll(members.get(node));
final Collection<JSwingRipplesEIGNode> neighbors=edgesToNeigbors(nodes,DIRECTION_CONSIDERED_CALLED_NODES_ONLY,NESTING_CONSIDERED_TOP_NODES_ONLY);
return neighbors
.toArray(new JSwingRipplesEIGNode[neighbors.size()]);
}
//-----Node-to-MemberNode neigbors ----
/**
* Returns member nodes that both call and are called by both the supplied node and it's member nodes if any
* <br>Works by calling {@link #edgesToNeigbors(Set, int, int)} method with predefined parameters
* @param node
* node, whose neighboring nodes should be returned
* @return
* neighboring nodes
*/
public JSwingRipplesEIGNode[] getAllMemberNodeNeighbors(
final JSwingRipplesEIGNode node) {
if (node==null) return new JSwingRipplesEIGNode[0];
final HashSet<JSwingRipplesEIGNode> nodes=new HashSet<JSwingRipplesEIGNode>();
nodes.add(node);
if (members.containsKey(node))
nodes.addAll(members.get(node));
final Collection<JSwingRipplesEIGNode> neighbors=edgesToNeigbors(nodes,DIRECTION_CONSIDERED_BOTH_CALLING_AND_CALLED,NESTING_CONSIDERED_MEMBER_NODES_ONLY);
return neighbors
.toArray(new JSwingRipplesEIGNode[neighbors.size()]);
}
/**
* Returns member nodes that call both the supplied node and it's member nodes if any
* <br>Works by calling {@link #edgesToNeigbors(Set, int, int)} method with predefined parameters
* @param node
* node, whose neighboring nodes should be returned
* @return
* neighboring nodes
*/
public JSwingRipplesEIGNode[] getIncomingMemberNodeNeighbors(
final JSwingRipplesEIGNode node) {
if (node==null) return new JSwingRipplesEIGNode[0];
final HashSet<JSwingRipplesEIGNode> nodes=new HashSet<JSwingRipplesEIGNode>();
nodes.add(node);
if (members.containsKey(node))
nodes.addAll(members.get(node));
final Collection<JSwingRipplesEIGNode> neighbors=edgesToNeigbors(nodes,DIRECTION_CONSIDERED_CALLING_NODES_ONLY,NESTING_CONSIDERED_MEMBER_NODES_ONLY);
return neighbors
.toArray(new JSwingRipplesEIGNode[neighbors.size()]);
}
/**
* Returns member nodes that are called by both the supplied node and it's member nodes if any
* <br>Works by calling {@link #edgesToNeigbors(Set, int, int)} method with predefined parameters
* @param node
* node, whose neighboring nodes should be returned
* @return
* neighboring nodes
*/
public JSwingRipplesEIGNode[] getOutgoingMemberNodeNeighbors(
final JSwingRipplesEIGNode node) {
if (node==null) return new JSwingRipplesEIGNode[0];
final HashSet<JSwingRipplesEIGNode> nodes=new HashSet<JSwingRipplesEIGNode>();
nodes.add(node);
if (members.containsKey(node))
nodes.addAll(members.get(node));
final Collection<JSwingRipplesEIGNode> neighbors=edgesToNeigbors(nodes,DIRECTION_CONSIDERED_CALLED_NODES_ONLY,NESTING_CONSIDERED_MEMBER_NODES_ONLY);
return neighbors
.toArray(new JSwingRipplesEIGNode[neighbors.size()]);
}
//-----Node-to-Any Node neigbors ----
/**
* Returns both top and member nodes that both call and are called by both the supplied node and it's member nodes if any
* <br>Works by calling {@link #edgesToNeigbors(Set, int, int)} method with predefined parameters
* @param node
* node, whose neighboring nodes should be returned
* @return
* neighboring nodes
*/
public JSwingRipplesEIGNode[] getAllAnyNodeNeighbors(
final JSwingRipplesEIGNode node) {
if (node==null) return new JSwingRipplesEIGNode[0];
final HashSet<JSwingRipplesEIGNode> nodes=new HashSet<JSwingRipplesEIGNode>();
nodes.add(node);
if (members.containsKey(node))
nodes.addAll(members.get(node));
final Collection<JSwingRipplesEIGNode> neighbors=edgesToNeigbors(nodes,DIRECTION_CONSIDERED_BOTH_CALLING_AND_CALLED,NESTING_CONSIDERED_BOTH_TOP_AND_MEMBER_NODES);
return neighbors
.toArray(new JSwingRipplesEIGNode[neighbors.size()]);
}
/**
* Returns both top and member nodes that call both the supplied node and it's member nodes if any
* <br>Works by calling {@link #edgesToNeigbors(Set, int, int)} method with predefined parameters
* @param node
* node, whose neighboring nodes should be returned
* @return
* neighboring nodes
*/
public JSwingRipplesEIGNode[] getIncomingAnyNodeNeighbors(
final JSwingRipplesEIGNode node) {
if (node==null) return new JSwingRipplesEIGNode[0];
final HashSet<JSwingRipplesEIGNode> nodes=new HashSet<JSwingRipplesEIGNode>();
nodes.add(node);
if (members.containsKey(node))
nodes.addAll(members.get(node));
final Collection<JSwingRipplesEIGNode> neighbors=edgesToNeigbors(nodes, DIRECTION_CONSIDERED_CALLING_NODES_ONLY,NESTING_CONSIDERED_BOTH_TOP_AND_MEMBER_NODES);
return neighbors
.toArray(new JSwingRipplesEIGNode[neighbors.size()]);
}
/**
* Returns both top and member nodes that are called by both the supplied node and it's member nodes if any
* <br>Works by calling {@link #edgesToNeigbors(Set, int, int)} method with predefined parameters
* @param node
* node, whose neighboring nodes should be returned
* @return
* neighboring nodes
*/
public JSwingRipplesEIGNode[] getOutgoingAnyNodeNeighbors(
final JSwingRipplesEIGNode node) {
if (node==null) return new JSwingRipplesEIGNode[0];
final HashSet<JSwingRipplesEIGNode> nodes=new HashSet<JSwingRipplesEIGNode>();
nodes.add(node);
if (members.containsKey(node))
nodes.addAll(members.get(node));
final Collection<JSwingRipplesEIGNode> neighbors=edgesToNeigbors(nodes,DIRECTION_CONSIDERED_CALLED_NODES_ONLY,NESTING_CONSIDERED_BOTH_TOP_AND_MEMBER_NODES);
return neighbors
.toArray(new JSwingRipplesEIGNode[neighbors.size()]);
}
// ------------------------------------- EIG edge operations ------------------------
/**
* Creates and adds to the JRipples EIG an edge connecting two supplied nodes:
* <br>( fromNode O-------------------------> toNode )<br>
* Please note, that the edge is omni-directional, that is fromNode->toNode does not imply toNode->fromNode.
* @param nodeFrom
* a node, from which this dependency originates
* @param nodeTo
* a node, to which which this dependency points
* @return
* an existing edge if one found in JRipples EIG,<br>
* a created edge if there was no such edge declared before,<br>
* <code>null</code> if one or both of the supplied nodes is <code>null</code>, or nodes are equal, or nodes are declared within the same top class
*/
public JSwingRipplesEIGEdge addEdge(final JSwingRipplesEIGNode nodeFrom,
final JSwingRipplesEIGNode nodeTo) {
if ((nodeTo == null) || (nodeFrom == null)) return null;
if (nodeTo == nodeFrom ) return null;
//enforces dependencies between different classes only
//if (JRipplesEIG.findTopNodeForMemberNode(nodeTo) == JRipplesEIG.findTopNodeForMemberNode(nodeFrom)) return null;
final JSwingRipplesEIGEdge oldEdge=getEdge(nodeFrom,nodeTo);
if (oldEdge!=null) {
oldEdge.setCount(oldEdge.getCount()+1);
return oldEdge;
}
final JSwingRipplesEIGEdge edge = new JSwingRipplesEIGEdge(this, nodeFrom, nodeTo);
//Add to the list of edges
edges.add(edge);
//Add to the adjacency matrix
if (!edgesAdjacency.containsKey(edge.getFromNode())) edgesAdjacency.put(edge.getFromNode(), new HashMap<JSwingRipplesEIGNode, JSwingRipplesEIGEdge>());
if (!nodesAdjacency.containsKey(edge.getFromNode())) nodesAdjacency.put(edge.getFromNode(), edge);//this one only records all adjacent nodes
final HashMap<JSwingRipplesEIGNode, JSwingRipplesEIGEdge> refferedNodes=edgesAdjacency.get(edge.getFromNode());
refferedNodes.put(edge.getToNode(),edge);
fireJRipplesEIGChanged(edge, JSwingRipplesEIGEdgeEvent.EDGE_ADDED);
return edge;
}
public void flattenEIG() {
final JSwingRipplesEIGEdge edges[]=getAllEdges();
final Set<JSwingRipplesEIGNode> nodesFrom=new HashSet<JSwingRipplesEIGNode>();
final Set<JSwingRipplesEIGNode> nodesTo=new HashSet<JSwingRipplesEIGNode>();
for (int i=0;i<edges.length;i++) {
nodesFrom.clear();
nodesTo.clear();
nodesFrom.add(edges[i].getFromNode());
nodesTo.add(edges[i].getToNode());
getAllParents(edges[i].getFromNode(),nodesFrom);
getAllParents(edges[i].getToNode(),nodesTo);
for (final Iterator <JSwingRipplesEIGNode> iterFrom=nodesFrom.iterator();iterFrom.hasNext();) {
final JSwingRipplesEIGNode nodeFrom=iterFrom.next();
for (final Iterator <JSwingRipplesEIGNode> iterTo=nodesTo.iterator();iterTo.hasNext();) {
final JSwingRipplesEIGNode nodeTo=iterTo.next();
addEdge(nodeFrom, nodeTo);
}
}
}
}
private void getAllParents (final JSwingRipplesEIGNode node, final Set<JSwingRipplesEIGNode> parents) {
if (node.isTop()) {
return;
} else {
final JSwingRipplesEIGNode parent=findParentNodeForMemberNode(node);
parents.add(parent);
getAllParents(parent,parents);
}
}
/**
* Returns previously created edge between two nodes, if one found in JRipples EIG.<br>
* Please note, that the edge is omni-directional, that is request for fromNode->toNode edge will not return toNode->fromNode
* edge even if such exists.
* @param nodeFrom
* a node, from which this dependency originates
* @param nodeTo
* a node, to which which this dependency points
* @return
* previously created edge between two nodes, if one found in JRipples EIG, <br>
* <code>null</code> otherwise
*/
public JSwingRipplesEIGEdge getEdge(final JSwingRipplesEIGNode nodeFrom,
final JSwingRipplesEIGNode nodeTo) {
if ((nodeFrom == null) || (nodeTo == null)) return null;
if (!edgesAdjacency.containsKey(nodeFrom)) return null;
final HashMap<JSwingRipplesEIGNode, JSwingRipplesEIGEdge> refferedNodes=edgesAdjacency.get(nodeFrom);
if (!refferedNodes.containsKey(nodeTo)) return null;
return refferedNodes.get(nodeTo);
}
/**
* Returns all the edges, registered with this EIG (that is, created with {@link #addEdge(JSwingRipplesEIGNode, JSwingRipplesEIGNode)} method).
* @return
* all the edges, registered with this EIG (that is, created with {@link #addEdge(JSwingRipplesEIGNode, JSwingRipplesEIGNode)} method).
*/
public JSwingRipplesEIGEdge[] getAllEdges() {
if (edges==null) return new JSwingRipplesEIGEdge[0];
return edges.toArray(new JSwingRipplesEIGEdge[edges.size()]);
}
/**
* Removes an edge from the JRipples EIG.
* @param edge
* edge to remove
*/
public void removeEdge(final JSwingRipplesEIGEdge edge) {
if (edges.contains(edge)) {
fireJRipplesEIGChanged(edge, JSwingRipplesEIGEdgeEvent.EDGE_REMOVED);
//Remove from common list
edges.remove(edge);
//Remove from Adjacency matrix
if (!edgesAdjacency.containsKey(edge.getFromNode())) return;
final HashMap<JSwingRipplesEIGNode, JSwingRipplesEIGEdge> refferedNodes=edgesAdjacency.get(edge.getFromNode());
if (!refferedNodes.containsKey(edge.getToNode())) return;
refferedNodes.remove(edge.getToNode());
}
}
/**
* Checks whether JRipples EIG contains an edge between two given nodes. <br>
* Please note, that this operation is omni-directional, that is <code>existsEdge(A,B)==true</code> does not imply <code>existsEdge(B,A)==true</code>
* @param nodeFrom
* a node, from which this edge originates
* @param nodeTo
* a node, to which this to which which this dependency points
* @return
* <code>true</code> if an edge between two given nodes exists, <br><code>false</code> otherwise
*/
public boolean existsEdge(final JSwingRipplesEIGNode nodeFrom,
final JSwingRipplesEIGNode nodeTo) {
if ((nodeFrom == null) || (nodeTo == null)) return false;
if (!edgesAdjacency.containsKey(nodeFrom)) return false;
final HashMap <JSwingRipplesEIGNode, JSwingRipplesEIGEdge> refferedNodes=edgesAdjacency.get(nodeFrom);
if (!refferedNodes.containsKey(nodeTo)) return false;
return true;
}
// ------------------------------------ EIG listener------------------------------------------------
/**
* Registers a {@link JSwingRipplesEIGListener} to receive updates on lyficycle and content events of JRipples EIG nodes and edges
* @param listener
* Listener to register
*/
public void addJRipplesEIGListener(
final JSwingRipplesEIGListener listener) {
synchronized(eigListeners) {
if (!eigListeners.contains(listener)) {
eigListeners.add(listener);
}
}
}
/**
* Unregisters a listener, previously registered with {@link #addJRipplesEIGListener(JSwingRipplesEIGListener)}.
* @param listener
* Listener to unregister
*/
public void removeJRipplesEIGListener(
final JSwingRipplesEIGListener listener) {
synchronized (eigListeners) {
eigListeners.remove(listener);
}
}
protected void fireJRipplesEIGChanged(final JSwingRipplesEIGNode item,
final int type, final String oldValue, final String newValue) {
final JSwingRipplesEIGNodeEvent event = new JSwingRipplesEIGNodeEvent(item,
type, oldValue, newValue);
notifyListeners(new JSwingRipplesEIGEvent(this,
new JSwingRipplesEvent[]{event}));
}
protected void fireJRipplesEIGChanged(final JSwingRipplesEIGEdge item,
final int type) {
final JSwingRipplesEIGEdgeEvent event = new JSwingRipplesEIGEdgeEvent(item, type);
notifyListeners(new JSwingRipplesEIGEvent(this, new JSwingRipplesEvent[]{event}));
}
private void notifyListeners(final JSwingRipplesEIGEvent event) {
//create working copy of listener list.
List<JSwingRipplesEIGListener> list;
synchronized (eigListeners) {
list = new LinkedList<JSwingRipplesEIGListener>(eigListeners);
}
for (final JSwingRipplesEIGListener l : list) {
l.jRipplesEIGChanged(event);
}
}
/**
* @return the project name.
*/
public JavaProject getJavaProject() {
return projectName;
}
/**
* @return the history.
*/
public History getHistory() {
return history;
}
}