/*******************************************************************************
* 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.ui.internal.presentation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.CatchClause;
import org.eclipse.jdt.core.dom.DoStatement;
import org.eclipse.jdt.core.dom.EnhancedForStatement;
import org.eclipse.jdt.core.dom.ForStatement;
import org.eclipse.jdt.core.dom.IfStatement;
import org.eclipse.jdt.core.dom.Statement;
import org.eclipse.jdt.core.dom.WhileStatement;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.swt.graphics.Color;
import org.eclipse.zest.custom.uml.viewers.IMessageGrouper;
import org.eclipse.zest.custom.uml.viewers.IMessageGrouping;
import org.eclipse.zest.custom.uml.viewers.MessageGrouping;
import org.eclipse.zest.custom.uml.viewers.UMLSequenceViewer;
import ca.uvic.chisel.javasketch.IProgramSketch;
import ca.uvic.chisel.javasketch.SketchPlugin;
import ca.uvic.chisel.javasketch.data.model.IActivation;
import ca.uvic.chisel.javasketch.data.model.IMessage;
import ca.uvic.chisel.javasketch.internal.ast.groups.ASTMessageGroupingTree;
import ca.uvic.chisel.javasketch.ui.ISketchColorConstants;
import ca.uvic.chisel.javasketch.ui.internal.preferences.ISketchPluginPreferences;
import ca.uvic.chisel.javasketch.ui.internal.presentation.metadata.PresentationData;
/**
* @author Del Myers
*
*/
public class ASTMessageGrouper implements IMessageGrouper {
public static class ASTMessageGrouping extends MessageGrouping implements Comparable<ASTMessageGrouping>, IAdaptable{
private ASTMessageGroupingTree node;
/**
* @param activationElement
*/
public ASTMessageGrouping(Object activationElement, ASTMessageGroupingTree node) {
super(activationElement);
this.node = node;
}
/* (non-Javadoc)
* @see java.lang.Comparable#compareTo(java.lang.Object)
*/
@Override
public int compareTo(ASTMessageGrouping that) {
//first, compare by indexes
if (this.getOffset() < that.getOffset()) {
return -1;
} else if (this.getOffset() > that.getOffset()) {
return 1;
} else {
//the offsets are equal, the smaller one is the
//one with the smaller length
if (this.getLength() < that.getLength()) {
return -1;
} else if (this.getLength() > that.getLength()){
return 1;
} else {
//the only other option is that they cover the same
//messages, use the AST to decide which one is
//internal
if (this.node.getNode().getStartPosition() <
that.node.getNode().getStartPosition()) {
return -1;
} else if (this.node.getNode().getStartPosition() >
that.node.getNode().getStartPosition()) {
return 1;
} else {
if (this.node.getNode().getLength() <
that.node.getNode().getLength()) {
return -1;
} else if (this.node.getNode().getLength() >
that.node.getNode().getLength()) {
return 1;
}
}
}
}
//can't decide which is greater.
return 0;
}
/* (non-Javadoc)
* @see org.eclipse.core.runtime.IAdaptable#getAdapter(java.lang.Class)
*/
@SuppressWarnings("unchecked")
@Override
public Object getAdapter(Class adapter) {
if (adapter.isAssignableFrom(ASTMessageGroupingTree.class)) {
return node;
}
return null;
}
/**
* @return the node
*/
public ASTMessageGroupingTree getNode() {
return node;
}
}
/* (non-Javadoc)
* @see org.eclipse.zest.custom.uml.viewers.IMessageGrouper#dispose()
*/
@Override
public void dispose() {
}
/* (non-Javadoc)
* @see org.eclipse.zest.custom.uml.viewers.IMessageGrouper#calculateGroups(org.eclipse.zest.custom.uml.viewers.UMLSequenceViewer, java.lang.Object, java.lang.Object[])
*/
@Override
public IMessageGrouping[] calculateGroups(UMLSequenceViewer viewer,
Object activationElement, Object[] children) {
// if (true) {
// return calculateGroups2(viewer, activationElement, children);
// }
HashMap<ASTMessageGroupingTree, ASTMessageGrouping> groups =
new HashMap<ASTMessageGroupingTree, ASTMessageGrouping>();
IPreferenceStore store = SketchPlugin.getDefault().getPreferenceStore();
LinkedList<ASTMessageGroupingTree> unusedLoops = new LinkedList<ASTMessageGroupingTree>();
LinkedList<ASTMessageGroupingTree> unusedGroups = new LinkedList<ASTMessageGroupingTree>();
boolean compactLoops = store.getBoolean(ISketchPluginPreferences.COMPACT_LOOPS_PREFERENCE);
boolean useCombinedFragments = store.getBoolean(ISketchPluginPreferences.DISPLAY_GROUPS_PREFERENCE);
if (!useCombinedFragments) {
return new IMessageGrouping[0];
}
if (activationElement instanceof IActivation) {
IActivation parent = (IActivation) activationElement;
IProgramSketch sketch = SketchPlugin.getDefault().getSketch(parent);
if (sketch != null) {
PresentationData pd = PresentationData.connect(sketch);
if (pd != null) {
try {
ASTMessageGroupingTree tree = pd.getGroups(parent);
if (tree == null) {
return new IMessageGrouping[0];
}
//search through the tree to find all loops
if (compactLoops) {
unusedGroups.add(tree);
while (unusedGroups.size() > 0) {
ASTMessageGroupingTree node = unusedGroups.removeFirst();
if (pd.isGroupVisible(parent, node)) {
if (node.isLoop()) {
unusedLoops.add(node);
}
for (ASTMessageGroupingTree child : node.getChildren()) {
unusedGroups.add(child);
}
}
}
}
for (int i = 0; i < children.length; i++) {
Object child = children[i];
if (child instanceof IMessage) {
ASTMessageGroupingTree node = tree.getMessageContainer((IMessage) child);
//put in the node and all of its parents.
while (node != null && node.getParent() != null) {
ASTMessageGrouping grouping = groups.get(node);
if (grouping == null) {
// if (compactLoops && node.isLoop()) {
grouping = new ASTMessageGrouping(activationElement, node);
grouping.setOffset(i);
groups.put(node, grouping);
// } else if (useCombinedFragments && !node.isLoop()) {
// grouping = new ASTMessageGrouping(activationElement, node);
// grouping.setOffset(i);
// groups.put(node, grouping);
// }
}
if (grouping != null) {
grouping.setLength(i - grouping.getOffset() + 1);
}
node = node.getParent();
}
}
}
} finally {
pd.disconnect();
}
}
}
}
for (ASTMessageGroupingTree node : groups.keySet()) {
updateGrouping(groups.get(node), node.getNode());
}
ASTMessageGrouping[] result = groups.values().toArray(new ASTMessageGrouping[groups.values().size()]);
Arrays.sort(result);
return result;
}
public IMessageGrouping[] calculateGroups2(UMLSequenceViewer viewer,
Object activationElement, Object[] children) {
HashMap<ASTMessageGroupingTree, ASTMessageGrouping> groups =
new HashMap<ASTMessageGroupingTree, ASTMessageGrouping>();
IPreferenceStore store = SketchPlugin.getDefault().getPreferenceStore();
ArrayList<ASTMessageGrouping> unusedGroups = new ArrayList<ASTMessageGrouper.ASTMessageGrouping>();
boolean compactLoops = store.getBoolean(ISketchPluginPreferences.COMPACT_LOOPS_PREFERENCE);
boolean useCombinedFragments = store.getBoolean(ISketchPluginPreferences.DISPLAY_GROUPS_PREFERENCE);
if (!useCombinedFragments) {
return new IMessageGrouping[0];
}
if (activationElement instanceof IActivation) {
IActivation parent = (IActivation) activationElement;
IProgramSketch sketch = SketchPlugin.getDefault().getSketch(parent);
if (sketch != null) {
PresentationData pd = PresentationData.connect(sketch);
if (pd != null) {
try {
ASTMessageGroupingTree tree = pd.getGroups(parent);
if (tree == null) {
return new IMessageGrouping[0];
}
LinkedList<ASTMessageGroupingTree> groupStack = new LinkedList<ASTMessageGroupingTree>();
//search through the tree to find all loops
groupStack.add(tree);
while (groupStack.size() > 0) {
ASTMessageGroupingTree node = groupStack.removeFirst();
if (pd.isGroupVisible(parent, node) || (!compactLoops && node.isLoop())) {
ASTMessageGrouping g = new ASTMessageGrouping(activationElement, node);
g.setOffset(-1);
g.setLength(0);
groups.put(node, g);
unusedGroups.add(g);
for (ASTMessageGroupingTree child : node.getChildren()) {
groupStack.add(child);
}
}
}
int unusedIndex = 0;
for (int i = 0; i < children.length; i++) {
Object child = children[i];
if (child instanceof IMessage) {
ASTMessageGroupingTree node = tree.getMessageContainer((IMessage) child);
ASTMessageGrouping grouping = groups.get(node);
int messageLineNumber = ((IMessage)child).codeLine();
if (grouping != null) {
if (grouping.getOffset() < 0) {
//update all the preceding offsets to
//be equal to this one.
for (int u = unusedIndex; u < unusedGroups.size(); unusedIndex++, u++) {
ASTMessageGrouping unused = unusedGroups.get(u);
if (unused.node.getFirstCodeLine() <= messageLineNumber) {
unused.setOffset(i);
} else {
unusedIndex = u;
break;
}
}
}
}
//update all the parents for the length
while (node != null && node.getParent() != null) {
grouping = groups.get(node);
if (grouping != null) {
grouping.setLength(i - grouping.getOffset() + 1);
}
node = node.getParent();
}
}
}
for (int i = unusedIndex; i < unusedGroups.size(); i++) {
ASTMessageGrouping unused = unusedGroups.get(i);
unused.setOffset(children.length);
}
} finally {
pd.disconnect();
}
}
}
}
for (Iterator<ASTMessageGrouping> i = unusedGroups.iterator(); i.hasNext();) {
ASTMessageGrouping group = i.next();
if (group.node.getNode().getNodeType() == ASTNode.METHOD_DECLARATION) {
i.remove();
} else if (group.getLength() <=0) {
//remove non-loop elements
if (!group.node.isLoop()) {
i.remove();
} else {
//walk up the list and remove children of empty
//loops
ASTMessageGroupingTree parent = group.node.getParent();
boolean remove = false;
while (parent != null) {
ASTMessageGrouping grouping = groups.get(parent);
if (grouping.node.isLoop() && grouping.getLength() <= 0) {
remove = true;
break;
}
parent = parent.getParent();
}
if (remove) {
i.remove();
}
}
}
}
for (ASTMessageGrouping group : unusedGroups) {
updateGrouping(group, group.node.getNode());
}
return unusedGroups.toArray(new ASTMessageGrouping[unusedGroups.size()]);
//return result;
}
/**
* Updates labels and colours for the grouping.
* @param currentGrouping
*/
private void updateGrouping(ASTMessageGrouping grouping, ASTNode node ) {
String text = "";
int i;
Color bg = null;
Color fg = null;
switch (node.getNodeType()) {
case ASTNode.IF_STATEMENT:
IfStatement ifStatement = (IfStatement) node;
text = "if (" + ifStatement.getExpression().toString() + ")";
//it could be an else-if, make sure
if (ifStatement.getParent().getNodeType() == ASTNode.IF_STATEMENT) {
if (ifStatement.equals(((IfStatement)ifStatement.getParent()).getElseStatement())) {
text = "else " + text;
}
}
fg = ISketchColorConstants.CONDITION_FG;
bg = ISketchColorConstants.CONDITION_BG;
break;
case ASTNode.WHILE_STATEMENT:
WhileStatement whileStatement = (WhileStatement) node;
text = "while (" + whileStatement.getExpression().toString() + ")";
fg = ISketchColorConstants.LOOP_FG;
bg = ISketchColorConstants.LOOP_BG;
break;
case ASTNode.DO_STATEMENT:
DoStatement doStatement = (DoStatement) node;
text = "do..while (" + doStatement.getExpression().toString() + ")";
fg = ISketchColorConstants.LOOP_FG;
bg = ISketchColorConstants.LOOP_BG;
break;
case ASTNode.FOR_STATEMENT:
ForStatement forStatement = (ForStatement) node;
List<?> initializers = forStatement.initializers();
List<?> updaters = forStatement.updaters();
text = "for (";
for (i=0; i < initializers.size(); i++) {
text += initializers.get(i).toString();
if (i < initializers.size()-1) {
text += ",";
}
}
text += ";";
if (forStatement.getExpression() != null) {
text += forStatement.getExpression();
}
text += ";";
for (i = 0; i < updaters.size(); i++) {
text += updaters.get(i).toString();
if (i < updaters.size()-1) {
text += ",";
}
}
text += ")";
fg = ISketchColorConstants.LOOP_FG;
bg = ISketchColorConstants.LOOP_BG;
break;
case ASTNode.ENHANCED_FOR_STATEMENT:
EnhancedForStatement enhancedForStatement = (EnhancedForStatement) node;
text = "for (" + enhancedForStatement.getExpression().toString() + ")";
fg = ISketchColorConstants.LOOP_FG;
bg = ISketchColorConstants.LOOP_BG;
break;
case ASTNode.TRY_STATEMENT:
text = "try";
fg = ISketchColorConstants.ERROR_FG;
bg = ISketchColorConstants.ERROR_BG;
break;
case ASTNode.CATCH_CLAUSE:
CatchClause catchClause = (CatchClause) node;
text = "catch (" + catchClause.getException().toString() + ")";
fg = ISketchColorConstants.ERROR_FG;
bg = ISketchColorConstants.ERROR_BG;
break;
default:
//get the else blocks
if (node instanceof Statement) {
Statement statement = (Statement) node;
if (statement.getParent() instanceof IfStatement) {
if (((IfStatement)statement.getParent()).getElseStatement() == statement) {
text = "else";
fg = ISketchColorConstants.CONDITION_FG;
bg = ISketchColorConstants.CONDITION_BG;
}
}
}
break;
}
if (grouping.node.isLoop()) {
ASTMessageGroupingTree[] siblings = grouping.node.getSiblings();
text = text + "[" + grouping.node.getIteration() + " of " + (siblings.length + 1) + "]";
}
grouping.setName(text);
grouping.setForeground(fg);
grouping.setBackground(bg);
}
}