/******************************************************************************* * 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.HashMap; import java.util.LinkedList; import java.util.List; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.IMethod; import org.eclipse.jdt.core.ITypeRoot; import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.IfStatement; import org.eclipse.jdt.core.dom.MethodDeclaration; import org.eclipse.jdt.core.dom.Statement; import org.eclipse.jface.text.Document; import org.eclipse.jface.text.IDocument; import ca.uvic.chisel.javasketch.SketchPlugin; import ca.uvic.chisel.javasketch.data.model.IActivation; import ca.uvic.chisel.javasketch.data.model.ICall; import ca.uvic.chisel.javasketch.data.model.IOriginMessage; import ca.uvic.chisel.javasketch.data.model.IReply; import ca.uvic.chisel.javasketch.data.model.ITraceClassMethod; import ca.uvic.chisel.javasketch.internal.JavaSearchUtils; import ca.uvic.chisel.javasketch.internal.ast.ASTMessageFinder; import ca.uvic.chisel.javasketch.internal.ast.ASTUTils; import ca.uvic.chisel.javasketch.internal.ast.GenericVisitor; /** * @author Del Myers * */ public class ASTLoopGroupCalculator { private static final String CURRENT_GROUPING = "ca.uvic.chisel.javasketch.activation.grouping"; /* (non-Javadoc) * @see org.eclipse.zest.custom.uml.viewers.IMessageGrouper#calculateGroups(org.eclipse.zest.custom.uml.viewers.UMLSequenceViewer, java.lang.Object, java.lang.Object[]) */ public static ASTMessageGroupingTree calculateGroups(IActivation activationElement) { //find the AST and define the groups IActivation activation = (IActivation) activationElement; IJavaElement element; try { HashMap<IOriginMessage, ASTNode> mappings = new HashMap<IOriginMessage, ASTNode>(); element = JavaSearchUtils.findElement(activation, new NullProgressMonitor()); if (element instanceof IMethod) { IMethod method = (IMethod) element; IDocument document = getDocumentFor(method); ASTNode root = ASTUTils.getASTFor(method.getDeclaringType()); if (root != null) { MethodDeclaration methodDeclaration =ASTUTils.findMethodDeclaration(root, method); if (methodDeclaration != null) { try { List<IOriginMessage> children = activation.getOriginMessages(); //a list of messages that aren't associated with an AST node: //they are pre-cursors to something else that must occur due to //reflection. List<IOriginMessage> unassociated = new LinkedList<IOriginMessage>(); for (IOriginMessage child : children) { ASTMessageFinder finder; if (child instanceof ICall) { finder = new ASTMessageFinder((ICall)child, document); } else if (child instanceof IReply) { finder = new ASTMessageFinder((IReply)child, document); } else { return null; } methodDeclaration.accept(finder); ASTNode messageNode = finder.getNode(); if (messageNode == null) { if (child instanceof IReply) { ASTMessageGroupingTree methodGroup = getGrouping(methodDeclaration); if (methodGroup != null) { methodGroup.addMessage(child, unassociated); unassociated.clear(); } //sometimes returns don't have line numbers continue; } unassociated.add((IOriginMessage) child); continue; } mappings.put((IOriginMessage) child, messageNode); ASTNode blockNode = findBlockParent(messageNode); if (blockNode == null) return null; ASTMessageGroupingTree grouping = getGrouping(blockNode); if (grouping == null) return null; IOriginMessage lastMessage = grouping.getLastMessage(); ASTNode lastNode = mappings.get(lastMessage); ASTNode thisNode = mappings.get(child); if (grouping.getLastCodeLine() <= child.codeLine()){ //if the called methods are different, then //assume that they are different methods on the //same line of code. Otherwise, it is a loop if (lastNode != null && thisNode != null) { //first, check to see if the last node is a child of this node //because if it is, than the evaluation will be opposite if (isChild(lastNode, thisNode)) { // if (lastNode.getStartPosition() < thisNode.getStartPosition()) { // grouping = resetGrouping(blockNode); // } } else if (isChild(thisNode, lastNode)) { if (lastNode.getStartPosition() <= thisNode.getStartPosition()) { grouping = resetGrouping(blockNode); } } else if (lastNode.getStartPosition() >= thisNode.getStartPosition()) { grouping = resetGrouping(blockNode); } } else if (similarMessages((IOriginMessage)grouping.getLastMessage(), (IOriginMessage)child)) { grouping = resetGrouping(blockNode); } } else if (grouping.getLastCodeLine() > child.codeLine()) { if (grouping.getNode() != methodDeclaration) { if (lastNode != null && thisNode != null) { if (!isChild(lastNode, thisNode)) { grouping = resetGrouping(blockNode); } } else { grouping = resetGrouping(blockNode); } } } grouping.addMessage(child, unassociated); unassociated.clear(); } //return the node left on the method declaration ASTMessageGroupingTree tree = (ASTMessageGroupingTree) methodDeclaration.getProperty(CURRENT_GROUPING); return tree; } finally { //make sure to clean up if (methodDeclaration != null) { methodDeclaration.accept( new GenericVisitor() { /* (non-Javadoc) * @see ca.uvic.chisel.javasketch.internal.ast.GenericVisitor#visitNode(org.eclipse.jdt.core.dom.ASTNode) */ @Override protected boolean visitNode(ASTNode node) { node.setProperty(CURRENT_GROUPING, null); return true; } }); } } } } } } catch (InterruptedException e) { //ignore and continue } catch (CoreException e) { SketchPlugin.getDefault().log(e); } return null; } /** * Checks to see if node c is a child of p * @param c * @param p * @return */ private static boolean isChild(ASTNode c, ASTNode p) { ASTNode parent = c.getParent(); while (parent != null) { if (parent.equals(p)) { return true; } parent = parent.getParent(); } return false; } private static boolean similarMessages(IOriginMessage lastMessage, IOriginMessage message) { if (lastMessage == null && message == null) { return true; } else if (lastMessage == null || message == null) { return false; } //they have to be origin messages if (!lastMessage.getClass().equals(message.getClass())) { return false; } IActivation lastTarget = lastMessage.getTarget().getActivation(); IActivation thisTarget = message.getTarget().getActivation(); ITraceClassMethod lastMethod = lastTarget.getMethod(); ITraceClassMethod thisMethod = thisTarget.getMethod(); return JavaSearchUtils.areMethodsSimilar(lastMethod, thisMethod); } /** * @param messageNode * @return */ private static ASTNode findBlockParent(ASTNode messageNode) { if (messageNode.getNodeType() == ASTNode.METHOD_DECLARATION) { return messageNode; } //search through the tree, up through the parents to find the nearest block ASTNode parent = messageNode.getParent(); while (parent != null) { switch (parent.getNodeType()) { case ASTNode.IF_STATEMENT: case ASTNode.WHILE_STATEMENT: case ASTNode.FOR_STATEMENT: case ASTNode.DO_STATEMENT: case ASTNode.ENHANCED_FOR_STATEMENT: case ASTNode.TRY_STATEMENT: case ASTNode.CATCH_CLAUSE: case ASTNode.METHOD_DECLARATION: return parent; default: //get the else blocks if (parent instanceof Statement) { Statement statement = (Statement) parent; if (statement.getParent() instanceof IfStatement) { if (((IfStatement)statement.getParent()).getElseStatement() == statement) { return statement; } } } break; } parent = parent.getParent(); } return null; } /** * @param blockNode * @param child * @param i * @param groupings * @return */ private static ASTMessageGroupingTree getGrouping(ASTNode blockNode) { ASTMessageGroupingTree currentGrouping = (ASTMessageGroupingTree) blockNode.getProperty(CURRENT_GROUPING); if (currentGrouping == null) { //first: if this blockNode is a method declaration, create a root group if (blockNode.getNodeType() == ASTNode.METHOD_DECLARATION) { currentGrouping = new ASTMessageGroupingTree(null, blockNode); blockNode.setProperty(CURRENT_GROUPING, currentGrouping); return currentGrouping; } else { ASTMessageGroupingTree parentGrouping = getGrouping(findBlockParent(blockNode)); currentGrouping = new ASTMessageGroupingTree(parentGrouping, blockNode); blockNode.setProperty(CURRENT_GROUPING, currentGrouping); return currentGrouping; } // // ASTMessageGroupingTree parentGrouping = // //create a new grouping for this node and all of its parents. // LinkedList<IMessageGrouping> created = new LinkedList<IMessageGrouping>(); // currentGrouping = new ASTMessageGroupingTree(activation, blockNode); // currentGrouping.setStartIndex(i); // currentGrouping.setEndIndex(i); // blockNode.setProperty(CURRENT_GROUPING, currentGrouping); // updateGrouping(currentGrouping); // created.addFirst(currentGrouping); // ASTNode parent = findBlockParent(blockNode); // while (parent != null) { // ASTMessageGroupingTree newGrouping = (ASTMessageGroupingTree) parent.getProperty(CURRENT_GROUPING); // if (newGrouping != null) { // //update it to include the new index // if (newGrouping.getEndIndex() < i) { // newGrouping.setEndIndex(i); // } // } else { // newGrouping = new ASTMessageGroupingTree(activation, parent); // parent.setProperty(CURRENT_GROUPING, newGrouping); // newGrouping.setStartIndex(i); // newGrouping.setEndIndex(i); // updateGrouping(newGrouping); // created.addFirst(newGrouping); // } // parent = findBlockParent(parent); // } // groupings.addAll(created); } return currentGrouping; } /** * Clears the current grouping for the block node, and all of its children. * @param blockNode * @param child * @param i * @param groupings * @return */ private static ASTMessageGroupingTree resetGrouping(ASTNode blockNode) { ASTNode loop = findLoopingParent(blockNode); if (loop != null) { ASTMessageGroupingTree loopGroup = (ASTMessageGroupingTree) loop.getProperty(CURRENT_GROUPING); int currentIteration = loopGroup.getIteration(); //clear the children loop.accept(new GenericVisitor(){ /* (non-Javadoc) * @see ca.uvic.chisel.javasketch.internal.ast.groups.GenericVisitor#visitNode(org.eclipse.jdt.core.dom.ASTNode) */ @Override protected boolean visitNode(ASTNode node) { node.setProperty(CURRENT_GROUPING, null); return true; } }); //create a new group for the loop ASTMessageGroupingTree newLoop = getGrouping(loop); newLoop.setIteration(currentIteration + 1); return getGrouping(blockNode); } return (ASTMessageGroupingTree) blockNode.getProperty(CURRENT_GROUPING); } /** * @param blockNode * @return */ private static ASTNode findLoopingParent(ASTNode node) { //search through the tree, up through the parents to find the nearest block ASTNode parent = node; ASTNode found = null; while (parent != null) { switch (parent.getNodeType()) { case ASTNode.WHILE_STATEMENT: case ASTNode.FOR_STATEMENT: case ASTNode.DO_STATEMENT: case ASTNode.ENHANCED_FOR_STATEMENT: found = parent; parent = null; break; default: parent = parent.getParent(); break; } } return found; } private static IDocument getDocumentFor(IJavaElement element) { try { if (element instanceof IMethod) { IMethod method = (IMethod) element; String source = null; ITypeRoot root = method.getTypeRoot(); source = root.getSource(); if (source != null) { return new Document(source); } } } catch (CoreException e) { SketchPlugin.getDefault().log(e); } return null; } }