/*----------------------------------------------------------------------------
Copyright (c)2000 TogetherSoft LLC. Patents pending. All rights reserved.
----------------------------------------------------------------------------*/
package com.togethersoft.modules.showcalltree;
import com.togethersoft.openapi.ide.IdeScript;
import com.togethersoft.openapi.ide.IdeStartup;
import com.togethersoft.openapi.ide.IdeContext;
import com.togethersoft.openapi.ide.IdeAccess;
import com.togethersoft.openapi.ide.message.IdeMessageManagerAccess;
import com.togethersoft.openapi.ide.message.IdeMessageType;
import com.togethersoft.openapi.ide.message.IdeMessagePage;
import com.togethersoft.openapi.ide.command.IdeCommandItem;
import com.togethersoft.openapi.ide.command.IdeCommandManager;
import com.togethersoft.openapi.ide.command.IdeCommandManagerAccess;
import com.togethersoft.openapi.ide.command.IdeCommandListener;
import com.togethersoft.openapi.ide.command.IdeCommandConstraints;
import com.togethersoft.openapi.ide.command.IdeCommandEvent;
import com.togethersoft.openapi.ide.resource.IdeResourceIconType;
import com.togethersoft.openapi.ide.project.IdeProjectManagerAccess;
import com.togethersoft.openapi.ide.editor.IdeEditorManager;
import com.togethersoft.openapi.ide.editor.IdeEditor;
import com.togethersoft.openapi.ide.editor.IdeEditorPositionConverter;
import com.togethersoft.openapi.ide.editor.IdeEditorSelectionType;
import com.togethersoft.openapi.ide.editor.IdeEditorVisibleArea;
import com.togethersoft.openapi.ide.editor.IdeEditorSelection;
import com.togethersoft.openapi.rwi.RwiElement;
import com.togethersoft.openapi.rwi.RwiModelAccess;
import com.togethersoft.openapi.sci.SciFunction;
import com.togethersoft.openapi.sci.SciOperation;
import com.togethersoft.openapi.sci.SciModelAccess;
import com.togethersoft.openapi.sci.SciClass;
import com.togethersoft.openapi.sci.SciCodeBlock;
import com.togethersoft.openapi.sci.SciFunctionCallExpression;
import com.togethersoft.openapi.sci.SciExpression;
import com.togethersoft.openapi.sci.SciMemberAccessExpression;
import com.togethersoft.openapi.sci.SciReferenceExpression;
import com.togethersoft.openapi.sci.SciElement;
import com.togethersoft.openapi.sci.SciVariable;
import com.togethersoft.openapi.sci.SciProperty;
import com.togethersoft.openapi.sci.visitor.SciExpressionVisitorAdapter;
import com.togethersoft.util.TextPosition;
import java.util.Vector;
import java.util.Hashtable;
import java.awt.Component;
import java.awt.event.MouseListener;
import java.awt.event.MouseEvent;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.ToolTipManager;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreeSelectionModel;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreePath;
import javax.swing.event.TreeWillExpandListener;
import javax.swing.event.TreeExpansionEvent;
/**
* This sample script shows the call tree of the selected operation.
* The list of method calls done within selected operation is displayed as a
* treeview in the separate tab of the bottom pane. Each call to a method is
* represented with a node. Each call to a method with availale source file
* is displayed as an expandable node, if there are calls made from this method.
* Selecting a line with such method also positions text editor to it.
* The treeview is implemented as JTree with redefined cell renderer component.
* @author TogetherSoft LLC
*/
public class ShowCallTree implements IdeScript, IdeStartup {
public void run(IdeContext context) {
// check if project is open
if (IdeProjectManagerAccess.getProjectManager().getActiveProject() == null) {
IdeMessageManagerAccess.printMessage(IdeMessageType.ERROR_MODAL, "No open project.");
return;
}
// the script can only be executed with one or more selected elements
RwiElement[] selectedRwiElements = context.getRwiElements();
if (selectedRwiElements == null || selectedRwiElements.length == 0) {
IdeMessageManagerAccess.printMessage(IdeMessageType.ERROR_MODAL, "No selection was made.");
return;
}
for (int i = 0; i < selectedRwiElements.length; i++) {
Object selectedElement = selectedRwiElements[i].getCodeElement();
if (selectedElement == null) { // does this element contain any code?
IdeMessageManagerAccess.printMessage(IdeMessageType.ERROR, "Operation not found.");
continue;
}
if (!(selectedElement instanceof SciOperation)) { // is the element an operator?
IdeMessageManagerAccess.printMessage(IdeMessageType.ERROR, "Selected object isn't an operation");
continue;
}
if (((SciOperation)selectedElement).hasProperty(SciProperty.ABSTRACT)) { // is the operation not abstract?
IdeMessageManagerAccess.printMessage(IdeMessageType.ERROR, "Selected operation is abstract");
continue;
}
new OperationPage((SciOperation)selectedElement);
}
}
/**
* This method is called at start up time and menu item initialization is put
* here, so that the script would also be available from elements' context menu.
*/
public void autorun() {
IdeCommandManager commandManager = IdeCommandManagerAccess.getCommandManager();
// create a command listener with instance of anonymous inner class
// to easily implement event processing
myItem = commandManager.createItem("CallTreeItem",
new IdeCommandConstraints("context = element, shapeType = Operation, location = popupMenu, parent = Scripts"), new IdeCommandListener() {
public void checkStatus(IdeCommandEvent event) {
// make the menu item enabled or disabled depending on context
IdeContext context = event.getElementContext();
if (context != null) { // the command can only be command executed on selected element
RwiElement[] selectedRwiElements = context.getRwiElements();
if (selectedRwiElements != null) {
for (int i = 0; i < selectedRwiElements.length; i++) {
SciElement selectedElement = (SciElement)selectedRwiElements[i].getCodeElement();
// make the command enabled only for non-abstract methods
if (selectedElement != null && selectedElement instanceof SciOperation &&
!((SciOperation)selectedElement).hasProperty(SciProperty.ABSTRACT)) {
myItem.setEnabled(true);
return;
}
}
}
}
myItem.setEnabled(false);
}
public void actionPerformed(IdeCommandEvent event) {
// actually process the command
(new ShowCallTree()).run(event.getElementContext());
}
});
myItem.setText("Show call tree"); // text to be displayed as menu command
}
private IdeCommandItem myItem;
/**
* Cell renderer component to be used in the JTree treeview. Displays name of operation in the cell with appropriate icon
* if the callee's class is known. Otherwise displays default gray icon.
*/
private class OperationTR extends DefaultTreeCellRenderer {
public OperationTR(Hashtable hashtable) {
myNodeOperationHashtable = hashtable;
}
public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus) {
super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus);
SciOperation operation = (SciOperation)myNodeOperationHashtable.get(value);
RwiElement element = null;
if (operation != null && !operation.isDeleted()) {
element = RwiModelAccess.getModel().findElement(operation.getUniqueName());
}
if (element != null) {
setToolTipText(SciModelAccess.getModel().getLanguageHelper(operation.getLanguage()).convertMemberQualifiedNameToPresentableForm(operation.getQualifiedName()));
Icon icon = IdeAccess.getResourceManager().getIcon(element, IdeResourceIconType.SMALL);
setIcon(icon);
} else {
setToolTipText(null);
java.net.URL iconResource = SciModelAccess.getModel().getClass().getResource("/Treeviews/tv-unknown_operation.gif");
if (iconResource != null) {
setToolTipText(iconResource.toString());
Icon icon = new ImageIcon(iconResource);
setIcon(icon);
}
}
return this;
}
private Hashtable myNodeOperationHashtable;
}
/** The class to represent a tab in the message window pane. */
private class OperationPage {
public OperationPage(SciFunction operation) {
// root node is the operation where the script was called
final DefaultMutableTreeNode root = new
DefaultMutableTreeNode(((SciClass)operation.getContainingScope()).getName() + "." + operation.getName() + "()");
root.setAllowsChildren(true);
myNodeOperationHashtable.put(root, operation);
root.add(new DefaultMutableTreeNode("loading..."));
// create default treeview model to be filled with data later in
// concurrent thread
myTreeModel = new DefaultTreeModel(root);
final JTree tree = new JTree();
tree.setModel(myTreeModel);
tree.putClientProperty("JTree.lineStyle", "Angled");
tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
tree.setShowsRootHandles(true);
tree.collapsePath(new TreePath(new Object[] { root }));
DefaultTreeCellRenderer renderer = new OperationTR(myNodeOperationHashtable);
tree.setCellRenderer(renderer);
// all operations with tree are done in cocurrent manner
tree.addTreeWillExpandListener(new TreeWillExpandListener() {
public void treeWillExpand(TreeExpansionEvent e) {
final DefaultMutableTreeNode node = (DefaultMutableTreeNode)e.getPath().getLastPathComponent();
if (node != null) {
IdeAccess.getIdeManager().addCommandToQueue(new Runnable() {
public void run() {
openNode(node);
}
});
}
}
public void treeWillCollapse(TreeExpansionEvent e) {
final DefaultMutableTreeNode node = (DefaultMutableTreeNode)e.getPath().getLastPathComponent();
if (node != null) {
IdeAccess.getIdeManager().addCommandToQueue(new Runnable() {
public void run() {
closeNode(node);
}
});
}
}
});
tree.addMouseListener(new MouseListener() {
public void mouseClicked(MouseEvent e) { }
public void mouseEntered(MouseEvent e) { }
public void mouseExited(MouseEvent e) { }
public void mousePressed(MouseEvent e) {
final DefaultMutableTreeNode node = (DefaultMutableTreeNode)tree.getLastSelectedPathComponent();
if (node != null) {
IdeAccess.getIdeManager().addCommandToQueue(new Runnable() {
public void run() {
selectOperation(node);
}
});
}
}
public void mouseReleased(MouseEvent e) { }
});
ToolTipManager.sharedInstance().registerComponent(tree);
String messagePageName = operation.getName();
int i = 0;
while (IdeAccess.getMessageManager().findPage(messagePageName) != null) {
i++;
messagePageName = operation.getName() + "(" + i + ")";
}
// put the pane with treeview to message window pane and make it active and visible
IdeMessagePage messagePage = IdeAccess.getMessageManager().openPage(messagePageName, new JScrollPane(tree), null);
messagePage.setTitle(messagePageName);
IdeAccess.getMessageManager().setActivePage(messagePage);
IdeAccess.getMessageManager().setPaneVisible(true);
}
private void openNode(DefaultMutableTreeNode parent) {
final Vector operations = new Vector();
// first, remove all existing children
while (parent.getChildCount() > 1) {
DefaultMutableTreeNode child = (DefaultMutableTreeNode)parent.getFirstChild();
myNodeOperationHashtable.remove(child);
closeNode(child);
myTreeModel.removeNodeFromParent(child);
}
DefaultMutableTreeNode emptyChild = (DefaultMutableTreeNode)parent.getFirstChild();
SciOperation operation = (SciOperation)myNodeOperationHashtable.get(parent);
if (operation != null && !operation.isDeleted()) {
final SciClass nodeClass = operation.getContainingClass();
final DefaultMutableTreeNode localParent = parent;
SciCodeBlock body = operation.getBody();
if (body != null) {
// facilitate going through expressions with help of visitor pattern;
// real visitors functionality defined in anonymous inner class
body.visitExpressions(new SciExpressionVisitorAdapter() {
public Object visitFunctionCallExpression(SciFunctionCallExpression functionCallExpression) {
SciExpression functionExpression = functionCallExpression.getFunction();
SciExpression objectExpression = null;
if (functionExpression instanceof SciMemberAccessExpression) {
objectExpression = ((SciMemberAccessExpression)functionExpression).getObjectExpression();
functionExpression = ((SciMemberAccessExpression)functionExpression).getMemberExpression();
}
SciOperation element = (SciOperation)((SciReferenceExpression)functionExpression).getReferencedElement();
DefaultMutableTreeNode child = null;
if (element != null) {
if (operations.indexOf(element) != -1) {
return null;
}
operations.addElement(element);
String childName = element.getName() + "()";
if (!element.hasProperty(SciProperty.CONSTRUCTOR)) {
SciClass childClass = element.getContainingClass();
if (!childClass.equals(nodeClass)) {
childName = childClass.getName() + "." + childName;
}
}
child = new DefaultMutableTreeNode(childName);
myNodeOperationHashtable.put(child, element);
myTreeModel.insertNodeInto(child, localParent, localParent.getChildCount());
if (!element.hasProperty(SciProperty.ABSTRACT)) {
myTreeModel.insertNodeInto(new DefaultMutableTreeNode(), child, child.getChildCount());
}
} else {
String nodeClassName = null;
if (objectExpression != null) {
if (objectExpression instanceof SciFunctionCallExpression) {
objectExpression = ((SciFunctionCallExpression)objectExpression).getFunction();
if (objectExpression instanceof SciMemberAccessExpression) {
objectExpression = ((SciMemberAccessExpression)objectExpression).getMemberExpression();
}
if (objectExpression instanceof SciReferenceExpression) {
SciFunction function = (SciFunction)((SciReferenceExpression)objectExpression).getReferencedElement();
if (function != null) {
nodeClassName = function.getReturnType().getText();
}
}
} else {
if (objectExpression instanceof SciMemberAccessExpression) {
objectExpression = ((SciMemberAccessExpression)objectExpression).getObjectExpression();
}
if (objectExpression instanceof SciReferenceExpression) {
SciElement objectElement = (SciElement)((SciReferenceExpression)objectExpression).getReferencedElement();
if (objectElement != null && objectElement instanceof SciVariable) {
nodeClassName = ((SciVariable)objectElement).getType().getText();
}
}
}
}
String nodeName = functionExpression.getText() + "()";
if (nodeClassName != null) {
nodeName = nodeClassName + "." + nodeName;
}
child = new DefaultMutableTreeNode(nodeName);
if (operations.indexOf(nodeName) != -1) {
return null;
}
operations.addElement(nodeName);
myTreeModel.insertNodeInto(child, localParent, localParent.getChildCount());
}
return null;
}
});
}
}
myNodeOperationHashtable.remove(emptyChild);
closeNode(emptyChild);
myTreeModel.removeNodeFromParent(emptyChild);
}
private void closeNode(DefaultMutableTreeNode parent) {
DefaultMutableTreeNode emptyNode = new DefaultMutableTreeNode("loading...");
myTreeModel.insertNodeInto(emptyNode, parent, parent.getChildCount());
while (parent.getChildCount() > 1) {
DefaultMutableTreeNode child = (DefaultMutableTreeNode)parent.getFirstChild();
myNodeOperationHashtable.remove(child);
closeNode(child);
myTreeModel.removeNodeFromParent(child);
}
}
/** Opens method's body in text editor window, if sourcecode is available. */
private void selectOperation(DefaultMutableTreeNode node) {
SciOperation operation = (SciOperation)myNodeOperationHashtable.get(node);
if (operation == null || operation.isDeleted()) {
return;
}
String fileName = operation.getContainingFile().getQualifiedName();
if (!fileName.endsWith(".java")) {
return;
}
IdeEditorManager editorManager = IdeAccess.getEditorManager();
IdeEditor editor = editorManager.findEditor(fileName);
if (editor == null) { // is this file already loaded in text editor?
editor = editorManager.openEditor();
editor.setFile(fileName);
}
editorManager.setActiveEditor(editor);
IdeEditorPositionConverter converter = editor.getPositionConverter();
TextPosition position = converter.offsetToCell(operation.getPositions().getStartOffset());
IdeEditorSelection selection = editor.getSelection(IdeEditorSelectionType.USER);
selection.setSelectionStart(new TextPosition(position.getLine(), 1));
selection.setSelectionEnd(new TextPosition(position.getLine() + 1, 1));
IdeEditorVisibleArea visibleArea = editor.getVisibleArea();
if (visibleArea == null || visibleArea.getFirstLine() == position.getLine()) {
return;
}
visibleArea.setFirstLine(position.getLine());
}
private Hashtable myNodeOperationHashtable = new Hashtable();
private DefaultTreeModel myTreeModel;
}
}
|