/*******************************************************************************
* Copyright (c) 2009 the CHISEL group and contributors.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Del Myers - initial API and implementation
*******************************************************************************/
package ca.uvic.chisel.javasketch.internal.ast.groups;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import ca.uvic.chisel.javasketch.data.model.IMessage;
import ca.uvic.chisel.javasketch.data.model.IOriginMessage;
import ca.uvic.chisel.javasketch.internal.ast.GenericVisitor;
/**
* @author Del Myers
*
*/
public class ASTMessageGroupingTree {
private ASTNode node;
//id for the ast node
private String nodeId;
private ASTMessageGroupingTree parent;
private int firstCodeLine = -2;
private int lastCodeLine = -1;
private int id;
private int iteration;
/**
* A simple class that visits a method declaration node and produces a unique identifier
* for an AST node within that declaration.
* @author Del Myers
*
*/
private class NodeIDVisitor extends GenericVisitor {
private boolean methodDeclarationVisited;
private LinkedList<Integer> identifier = new LinkedList<Integer>();
private boolean found = false;
protected boolean visitNode(ASTNode v) {
if (!methodDeclarationVisited && !(v instanceof MethodDeclaration)) {
return false;
}
if (!methodDeclarationVisited) {
//this is the first method declaration, initialize everything
identifier = new LinkedList<Integer>();
identifier.add(0);
methodDeclarationVisited = true;
}
if (v == node) {
found = true;
return false;
}
int count = identifier.removeLast();
identifier.push(count+1);
//get set up for the next child
identifier.add(0);
return true;
}
/* (non-Javadoc)
* @see ca.uvic.chisel.javasketch.internal.ast.GenericVisitor#endVisitNode(org.eclipse.jdt.core.dom.ASTNode)
*/
@Override
protected void endVisitNode(ASTNode node) {
if (!found) {
if (identifier.size() > 0) {
identifier.removeLast();
}
}
super.endVisitNode(node);
}
/**
* @return the identifier
*/
public String getIdentifier() {
StringBuilder builder = new StringBuilder();
boolean first = true;
for (Integer i : identifier) {
if (!first) {
builder.append('.');
}
first = false;
builder.append(i);
}
return builder.toString();
}
}
/**
* We use the sequence number of the messages because they uniquely identify
* a message in a thread, and they are ordered.
*/
private TreeSet<String> messages;
private ArrayList<ASTMessageGroupingTree> children;
private IOriginMessage lastMessage;
public ASTMessageGroupingTree(ASTMessageGroupingTree parent, ASTNode node) {
this.node = node;
this.parent = parent;
iteration = 1;
messages = new TreeSet<String>();
children = new ArrayList<ASTMessageGroupingTree>();
id = 0;
if (parent != null) {
parent.addChild(this);
}
}
/**
* @param astMessageGroupingTree
*/
private void addChild(ASTMessageGroupingTree child) {
children.add(child);
child.id = children.size();
}
/**
* Returns a string that uniquely identifies this tree node within
* the hierarchy;
* @return
*/
public String getIdentifier() {
if (getParent() == null) {
return "0";
}
return getParent().getIdentifier() + "." + id;
}
void setIteration(int iteration) {
this.iteration = iteration;
}
/**
* If this group represents a loop, this method returns the iteration
* of that loop that this group represents within the hierarchy. To
* find out the actual number of times that this group iterated, one
* must walk up the hierarchy, to query the number of iterations on
* the parents as well.
*
* If this group does not represent a loop, this method will always
* return 1.
*/
public int getIteration() {
return iteration;
}
/**
* Returns the groups in the parent which are on the same AST Node.
* This, for example, will return all of the iterations of a loop.
* Returns an empty list if there are no other siblings.
* @return the siblings.
*/
public ASTMessageGroupingTree[] getSiblings() {
LinkedList<ASTMessageGroupingTree> siblings =
new LinkedList<ASTMessageGroupingTree>();
if (getParent() != null) {
for (ASTMessageGroupingTree sibling : getParent().children) {
if (sibling != this) {
if (sibling.node == this.node) {
siblings.add(sibling);
}
}
}
}
return siblings.toArray(new ASTMessageGroupingTree[siblings.size()]);
}
/**
* Gets all of the iterations of the node equivalent to this one.
* @return
*/
public ASTMessageGroupingTree[] getIterations() {
if (!isLoop()) {
return new ASTMessageGroupingTree[0];
}
LinkedList<ASTMessageGroupingTree> loops =
new LinkedList<ASTMessageGroupingTree>();
if (getParent() != null) {
for (ASTMessageGroupingTree sibling : getParent().children) {
if (sibling == this || sibling.getNodeID().equals(getNodeID())) {
loops.add(sibling);
}
}
}
return loops.toArray(new ASTMessageGroupingTree[loops.size()]);
}
/**
* Returns true iff the AST node for this group represents a loop.
* @return true iff the AST node for this group represents a loop.
*/
public boolean isLoop() {
switch (node.getNodeType()) {
case ASTNode.ENHANCED_FOR_STATEMENT:
case ASTNode.FOR_STATEMENT:
case ASTNode.WHILE_STATEMENT:
case ASTNode.DO_STATEMENT:
return true;
}
return false;
}
public void addMessage(IOriginMessage message, List<IOriginMessage> associated) {
if (message.codeLine() < firstCodeLine || firstCodeLine == -2) {
firstCodeLine = message.codeLine();
}
if (message.codeLine() > lastCodeLine) {
lastCodeLine = message.codeLine();
}
if (messages.size() == 0 || lastCodeLine == message.codeLine()) {
lastMessage = message;
}
for (IOriginMessage m :associated) {
messages.add(m.getIdentifier());
}
messages.add(message.getIdentifier());
}
/**
* Returns the identifiers for the messages contained in this group.
* @return
*/
public Set<String> getMessageIdentifiers() {
TreeSet<String> identifiers = new TreeSet<String>();
LinkedList<ASTMessageGroupingTree> children = new LinkedList<ASTMessageGroupingTree>();
children.add(this);
while(children.size() > 0) {
ASTMessageGroupingTree child = children.removeFirst();
identifiers.addAll(child.messages);
children.addAll(child.getChildren());
}
return Collections.unmodifiableSet(identifiers);
}
/**
* Returns the first line of code covered by this group
* @return the firstCodeLine
*/
public int getFirstCodeLine() {
return firstCodeLine;
}
/**
* Returns the last line of code covered by this group
* @return the lastCodeLine
*/
public int getLastCodeLine() {
return lastCodeLine;
}
/**
* Convenience method for querying while setting up loops
* @return
*/
public IOriginMessage getLastMessage() {
return lastMessage;
}
public boolean containsMessage(IMessage message) {
return messages.contains(message.getIdentifier());
}
/**
* Finds the container in this tree for the given message, or null if it coud not be found.
* @param message
* @return
*/
public ASTMessageGroupingTree getMessageContainer(IMessage message) {
if (containsMessage(message)) {
return this;
}
for (ASTMessageGroupingTree child : children) {
ASTMessageGroupingTree found = child.getMessageContainer(message);
if (found != null) {
return found;
}
}
return null;
}
public List<ASTMessageGroupingTree> getChildren() {
return Collections.unmodifiableList(children);
}
/**
* @return the parent
*/
public ASTMessageGroupingTree getParent() {
return parent;
}
/**
* @return
*/
public ASTNode getNode() {
return node;
}
/**
* Returns an identifier for the ast node for this tree,
* @return
*/
public String getNodeID() {
if (nodeId == null) {
//find the declaring method
ASTNode declaringMethod = node;
while (declaringMethod != null && !(declaringMethod instanceof MethodDeclaration)) {
declaringMethod = declaringMethod.getParent();
}
if (declaringMethod != null) {
NodeIDVisitor v = new NodeIDVisitor();
declaringMethod.accept(v);
nodeId = v.getIdentifier();
}
}
return nodeId;
}
}