FileDocCategorySizeDatePackage
ShowCallTree.javaAPI DocExample22345Thu Feb 17 20:00:56 GMT 2000com.togethersoft.modules.showcalltree

ShowCallTree.java

/*----------------------------------------------------------------------------
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;
    }
}