/*-------------------------------------------------------------------------------
Copyright (c)2000 TogetherSoft Corporation. Patents pending. All rights reserved.
-------------------------------------------------------------------------------*/
package com.togethersoft.modules.showinheritance;

import com.togethersoft.modules.showdependencies.ShowDependencies;

import com.togethersoft.openapi.ide.IdeScript;
import com.togethersoft.openapi.ide.IdeContext;
import com.togethersoft.openapi.ide.project.IdeProjectManagerAccess;
import com.togethersoft.openapi.ide.diagram.IdeDiagramManagerAccess;
import com.togethersoft.openapi.ide.diagram.IdeDiagramManager;
import com.togethersoft.openapi.ide.diagram.IdeDiagram;
import com.togethersoft.openapi.ide.window.IdeWindowManager;
import com.togethersoft.openapi.ide.window.IdeWindowManagerAccess;
import com.togethersoft.openapi.ide.window.IdeDialogType;
import com.togethersoft.openapi.ide.message.IdeMessageManagerAccess;
import com.togethersoft.openapi.ide.message.IdeMessageManager;
import com.togethersoft.openapi.ide.message.IdeMessagePage;
import com.togethersoft.openapi.ide.message.IdeMessageView;
import com.togethersoft.openapi.ide.message.IdeMessageType;
import com.togethersoft.openapi.ide.resource.IdeResourceManagerAccess;
import com.togethersoft.openapi.ide.resource.IdeResourceIconType;
import com.togethersoft.openapi.rwi.RwiDiagram;
import com.togethersoft.openapi.rwi.RwiNode;
import com.togethersoft.openapi.rwi.RwiElement;
import com.togethersoft.openapi.rwi.RwiProperty;
import com.togethersoft.openapi.rwi.RwiModelAccess;
import com.togethersoft.openapi.rwi.RwiShapeType;
import com.togethersoft.openapi.rwi.enum.RwiNodeReferenceEnumeration;
import com.togethersoft.openapi.sci.SciPackage;
import com.togethersoft.openapi.sci.SciClass;
import com.togethersoft.openapi.sci.SciInheritanceList;
import com.togethersoft.openapi.sci.SciInheritance;
import com.togethersoft.openapi.sci.SciReference;
import com.togethersoft.openapi.sci.SciProperty;
import com.togethersoft.openapi.sci.SciModelAccess;
import com.togethersoft.openapi.sci.SciLanguage;
import com.togethersoft.openapi.sci.enum.SciInheritanceEnumeration;
import javax.swing.border.EtchedBorder;
import javax.swing.tree.TreeNode;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreePath;
import javax.swing.Icon;
import javax.swing.JTree;
import javax.swing.JPanel;
import javax.swing.JButton;
import javax.swing.SwingConstants;
import javax.swing.JScrollPane;
import javax.swing.JLabel;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.awt.Component;
import java.awt.BorderLayout;
import java.util.Hashtable;
import java.util.Vector;
import java.util.Enumeration;

/**
 * This sample module scans the current class diagram and generates
 * the inheritance tree for the classes and interfaces in this diagram
 * (including shortcuts).
 *
 * The tree is JavaDoc-like (click the Tree hyperlink in navigation toolbar
 * in the JavaDoc-generated documentation), except it is smarter: just like
 * JavaDoc it generates two separate hierarchies for classes (derived from
 * java.lang.Object ... heh, you know that;) ) and for interfaces;
 * but for interfaces it also includes hierarchy of classes implementing
 * each interface. This is very nice feature when you have a lot of interfaces
 * and classes, and after you have browsed the hierarcy of interfaces,
 * you wish to dig deeper into the hierarchy of classes implementing
 * the interface you are interested in.
 *
 * @version 3.0.0
 * @author TogetherSoft
 */
public class ShowInheritance implements IdeScript
{
   /**
    * Runs this module.
    *
    * @param context the IdeContext instance containing
    * the selection information at the moment the module was called.
    */
    public void run(IdeContext context)
    {
        // printing information message
        IdeMessageManagerAccess.printMessage
            (IdeMessageType.INFORMATION, "ShowInheritanceTree module: started");

        // if project is opened
        if (IdeProjectManagerAccess.getProjectManager().getActiveProject() == null)
        {
            // no open projects
            // show modal window with error description
            IdeMessageManagerAccess.printMessage
                (IdeMessageType.ERROR_MODAL, "No open project");
            // printing information message
            IdeMessageManagerAccess.printMessage
                (IdeMessageType.INFORMATION, "ShowInheritanceTree module: finished");
            return;
        }

        // getting active diagram
        IdeDiagram activeIdeDiagram =
            IdeDiagramManagerAccess.getDiagramManager().getActiveDiagram();

        // if ther is not open diagram
        if (activeIdeDiagram == null)
        {
            IdeMessageManagerAccess.printMessage
                (IdeMessageType.ERROR_MODAL, "No open diagram");
            IdeMessageManagerAccess.printMessage
                (IdeMessageType.INFORMATION, "ShowInheritanceTree module: finished");
            return;
        }

        // gettitng RWI active diagram
        RwiDiagram activeRwiDiagram = activeIdeDiagram.getRwiDiagram();

        // if it's not class diagram
        if (!RwiShapeType.CLASS_DIAGRAM.equals
            (activeRwiDiagram.getProperty(RwiProperty.SHAPE_TYPE)))
        {
            IdeMessageManagerAccess.printMessage
                (IdeMessageType.ERROR_MODAL, "No class diagram active");
            IdeMessageManagerAccess.printMessage
                (IdeMessageType.INFORMATION, "ShowInheritanceTree module: finished");
            return;
        }

        // creating faking class and interface to obtain the icons
        SciClass fakeSciClass =
            SciModelAccess.getModel().getFactory(SciLanguage.JAVA).newClass();

        // getting class icon
        RwiElement element = RwiModelAccess.getModel().findNode(fakeSciClass);
        if (element != null)
        {
            classIcon =
                IdeResourceManagerAccess.getResourceManager().getIcon
                (element, IdeResourceIconType.SMALL);
        }

        // setting faking class interface property to obtain interface icon
        fakeSciClass.setProperty(SciProperty.INTERFACE, true);

        // getting interface icon
        element = RwiModelAccess.getModel().findNode(fakeSciClass);
        if (element != null)
        {
            interfaceIcon =
                IdeResourceManagerAccess.getResourceManager().getIcon
                (element, IdeResourceIconType.SMALL);
        }

        // initialization

        language = null;

        baseObjectsFirst = new DataStorage();
        baseObjectsLast = new DataStorage();

        processed = new Hashtable();

        rootForClasses = new Fake("java.lang.Object", false);

        // collecting required information for building the tree
        createData(activeRwiDiagram);

        // building the tree visual component
        final JTree tree = new JTree(getTreeRoot());
        tree.setRootVisible(false);
        tree.setShowsRootHandles(true);
        tree.putClientProperty("JTree.lineStyle", "Angled");
        tree.setCellRenderer(new TreeRend());
        tree.addMouseListener(new MouseAdapter()
        {
            public void mouseClicked(MouseEvent e)
            {
                JTree jtree = (JTree)e.getSource();
                TreePath selPath = jtree.getPathForLocation(e.getX(), e.getY());
                if (selPath != null)
                {
                    if (e.getClickCount() == 2)
                    {
                        Object containingObject =
                            ((DefaultMutableTreeNode)selPath.getLastPathComponent()).getUserObject();
                        if (containingObject instanceof SciClass)
                        {
                            SciClass theClass = (SciClass)containingObject;
                            if (!theClass.isDeleted())
                            { // is everything ok?
                                ShowDependencies.openAndSelect(theClass, theClass.getPositions());
                            }
                        }
                    }
                }
            }
        });

        JPanel panel = new JPanel();
        panel.setLayout(new BorderLayout());
        panel.add(tree, "Center");
        final JButton reverseButton = new JButton(" Reverse the tree ");
        final JLabel label = new JLabel(getLabelText(), SwingConstants.CENTER);
        reverseButton.addActionListener(new ActionListener()
        {
            public void actionPerformed(ActionEvent e)
            {
                baseObjectsGoFirst = !baseObjectsGoFirst;
                label.setText(getLabelText());
                ((DefaultTreeModel)tree.getModel()).setRoot(getTreeRoot());
            }
        });

        JPanel eastPanel = new JPanel();
        eastPanel.setLayout(new BorderLayout());
        eastPanel.setBorder(new EtchedBorder());
        eastPanel.add(reverseButton, "North");
        eastPanel.add(label, "Center");
        panel.add(eastPanel, "East");

        JScrollPane scrollPane = new JScrollPane(panel);
        final String pageID = "Inheritance tree for the diagram " +
            activeRwiDiagram.getProperty(RwiProperty.NAME);

        // getting message manager
        IdeMessageManager messageManager = IdeMessageManagerAccess.getMessageManager();

        // if there is page with identifier pageID
        if (messageManager.findPage(pageID) != null)
        {
            // close page
            messageManager.closePage(messageManager.findPage(pageID));
        }

        // open page
        IdeMessagePage page = messageManager.openPage(pageID, scrollPane, null);
        page.setTitle(pageID); // setting page title
        messageManager.setActivePage(page); // making the page active
        messageManager.setPaneVisible(true); //making the message pane visible

        // printing final information message
        IdeMessageManagerAccess.printMessage
            (IdeMessageType.INFORMATION, "ShowInheritanceTree module: finished");

        return;
    }

    /**
     * Returns specified label text
     *
     * @return label text
     */
    private String getLabelText()
    {
        return baseObjectsGoFirst ? "Now parents are first" : "Now children are first";
    }

    /**
     * Builds two data models representing two different modes of the tree.
     * Note: this method analyzes the shortcuts as well.
     *
     * @param diagram the RwiDiagram to work with.
     */
    private void createData(RwiDiagram diagram)
    {
        // getting references to nodes
        RwiNodeReferenceEnumeration nodeRefs = diagram.nodeReferences();

        // processing each reference
        while (nodeRefs.hasMoreElements())
        {
            // getting node
            RwiNode nextNode = nodeRefs.nextRwiNodeReference().getNode();

            // if node is empty
            if (nextNode == null)
            {
                continue;
            }

            // if node is not class or inteface
            if (!(nextNode.getCodeElement() instanceof SciClass))
            {
                continue;
            }

            // getting next node code element
            SciClass nextSciClass = (SciClass)nextNode.getCodeElement();

            // getting language
            if ( language == null )
            {
                language = nextSciClass.getLanguage();
            }

            // analyze class
            analyzeSciClass(nextSciClass, null, false);
        }
    }

    /**
     * Returns an Object representing the specified SciClass in the data model
     * (DataStorage instances).
     * For SciClasses return SciClasses themselves, but if SciClass is null,
     * it returns corresponding "wrapper" - the Fake object.
     * The returned object can be used in further operations with the data model
     * (like adding a child to it, removing from the top etc.)
     *
     * @param theClass SciClass to be processed.
     * @param ifClassIsNull SciReference passed to the method in case theClass
     * is null. This can happen when Together has only reference to a class
     * (like "class MyClass extends NotInTheCLASSPATHClass") but cannot
     * get SciClass object corresponding to this reference. Then we put it into
     * the root (if it is a class, under "java.lang.Object"), and use the
     * reference to extract the name of the referenced class.
     * @param isInterface additional information used only when theClass is null,
     * to determine whether the referenced element is a class or an interface.
     * It is used to create corresponding Fake object with the right icon.
     * @return an Object representing the specified SciClass in the data model
     */
    private Object analyzeSciClass
    (SciClass sciClass, SciReference ifClassIsNull, boolean isInterface)
    {
        if (sciClass == null)
        { // Together has only a reference to a class (interface)
            String nonFoundName = ifClassIsNull.getText();
            if (processed.containsKey(nonFoundName))
            {
                return processed.get(nonFoundName);
            }
            Fake insertedObject = null;
            System.out.println("name + " + nonFoundName);

            if (("Object".equals(nonFoundName)) ||
                ("java.lang.Object".equals(nonFoundName)))
            {
                // without this check if someone uses "extends Object",
                // then Object will appear twice since we automatically
                // create it as a parent of all classes
                insertedObject = rootForClasses;
                baseObjectsFirst.putToTheTop(insertedObject);
                baseObjectsLast.putToTheTop(insertedObject);
            }
            else
            {
                insertedObject =
                    new Fake(nonFoundName + " (not in the CLASSPATH)", isInterface);

                // of it's interface or C++ class
                if (isInterface || language.equals(SciLanguage.CPP))
                {
                    baseObjectsFirst.putToTheTop(insertedObject);
                    baseObjectsLast.putToTheTop(insertedObject);
                }
                else
                { // it's java class
                    baseObjectsFirst.putToTheTop(rootForClasses);
                    baseObjectsFirst.putOneUnderAnother(insertedObject, rootForClasses);
                    baseObjectsLast.putToTheTop(insertedObject);
                    baseObjectsLast.putOneUnderAnother(rootForClasses, insertedObject);
                }
            }

            processed.put(nonFoundName, insertedObject);
            return insertedObject;
        }

        if (processed.containsKey(sciClass))
        {
            return sciClass;
        }

        int countOfClassesThisSciClassExtends = 0;

        // we are working with "extends" section.
        SciInheritanceList extendsList = sciClass.getExtendsList();
        SciInheritanceEnumeration extendEnum = extendsList.inheritances();
        while (extendEnum.hasMoreElements())
        {
            SciInheritance extend = extendEnum.nextSciInheritance();
            SciClass target = (SciClass)extend.getReferencedElement();
            countOfClassesThisSciClassExtends++;
            Object insertedTarget = analyzeSciClass(target, extend,
                (sciClass.hasProperty(SciProperty.INTERFACE)) ? true : false);
            baseObjectsFirst.putOneUnderAnother(sciClass, insertedTarget);
            baseObjectsLast.removeFromTheTop(insertedTarget);
            baseObjectsLast.putToTheTop(sciClass);
            baseObjectsLast.putOneUnderAnother(insertedTarget, sciClass);
        }

        // we are working with "implements" section.
        SciInheritanceList implementsList = sciClass.getImplementsList();
        if (implementsList != null)
        {
            SciInheritanceEnumeration implEnum = implementsList.inheritances();
            while (implEnum.hasMoreElements())
            {
                SciInheritance implem = implEnum.nextSciInheritance();
                SciClass target = (SciClass)implem.getReferencedElement();
                Object insertedTarget = analyzeSciClass(target, implem, true);
                baseObjectsFirst.putOneUnderAnother(sciClass, insertedTarget);
                baseObjectsLast.putToTheTop(sciClass);
                baseObjectsLast.putOneUnderAnother(insertedTarget, sciClass);
            }
        }

        // if class (interface) doesn't extend anything
        if (countOfClassesThisSciClassExtends == 0)
        {
            // if it's interface or C++ class
            if (sciClass.hasProperty(SciProperty.INTERFACE) ||
                SciLanguage.CPP.equals(sciClass.getLanguage()))
            {
                baseObjectsFirst.putToTheTop(sciClass);
                baseObjectsLast.putToTheTop(sciClass);
            }
            else
            { // it's java class
                baseObjectsFirst.putToTheTop(rootForClasses);
                if ("java.lang.Object".equals(sciClass.getQualifiedName()))
                {
                    // if sciClass is java.lang.Object (that means Object it is
                    // in the classpath), then there is no need to insert it,
                    // - we have a fake (otherwise we'll have two java.lang.Objects)
                    return rootForClasses;
                }
                else
                {
                    baseObjectsFirst.putOneUnderAnother(sciClass, rootForClasses);
                    baseObjectsLast.putToTheTop(sciClass);
                    baseObjectsLast.putOneUnderAnother(rootForClasses, sciClass);
                }
            }
        }
        processed.put(sciClass, new Object()); // value does not matter here
        return sciClass; // if sciClass is null it is used in the data model directly
    }

    /**
     * Returns the root TreeNode (with all corresponding contents)
     * for the current mode of the tree.
     *
     * @return the root TreeNode object
     */
    private TreeNode getTreeRoot()
    {
        Hashtable data =
            (baseObjectsGoFirst ? baseObjectsFirst : baseObjectsLast).getTreeData();
        return hashtableToRootTransformer.getRootNode(data);
    }

    // these two objects represent data models for the two states of the tree
    private DataStorage baseObjectsFirst;
    private DataStorage baseObjectsLast;

    // This Hashtable is used to keep information about processed classes/interfaces.
    // The key is either non-null SciClass object, or a String
    // (with the name of a class/interface).
    // For both cases the corresponing Object value is a representation in the
    // data model of the processed element. Having this Object,
    // it is possible to use it in the DataStorage methods.
    // (For example, to add children, to add/remove to the top etc.)
    private Hashtable processed;

    // This is a converter from HashTable (the data model) to the root node
    // (with corresponding contents).
    // Why not to operate in the DataStorage with TreeNodes directly?
    // It is much easier to work with Hashtables and only at the end
    // to build up the nodes hierarchy.
    private Transformer hashtableToRootTransformer = new Transformer();

    private boolean baseObjectsGoFirst = true; //defines the mode of the tree
    private Fake rootForClasses; //Fake object representing "java.lang.Object"
    private String language; // language
    private Icon classIcon;  // class icon
    private Icon interfaceIcon;  // interface icon

    /**
     * Transforms a tree data model represented by a Hashtable of Hashtables
     * to the TreeNodes hierarchy.
     * The nodes representing interfaces come first.
     */
    class Transformer
    {
        /*
         * Returns parent TreeNode object.
         */
        public TreeNode getRootNode(Hashtable table)
        {
            return createParentForChildren("Root node", getNodesForKeysOfHT(table));
        }

        /*
         * Creates parens node for specified parent object and nodes.
         */
        private DefaultMutableTreeNode createParentForChildren
        (Object parentUserObject, Vector nodes)
        {
            DefaultMutableTreeNode parent =
                new DefaultMutableTreeNode(parentUserObject);
            final int nodesSize = nodes.size();
            for (int i = 0; i < nodesSize; i++)
            {
                parent.add((DefaultMutableTreeNode)nodes.elementAt(i));
            }
            return parent;
        }

        /*
         * Returns nodes vector for specified keys.
         */
        private Vector getNodesForKeysOfHT(Hashtable table)
        {
            Vector vectorWithInterfacesFirst = new Vector();
            Enumeration keys = table.keys();
            while (keys.hasMoreElements())
            {
                Object nextKey = keys.nextElement();
                int position = 0;
                if (((nextKey instanceof SciClass) && (((SciClass)nextKey).hasProperty(SciProperty.INTERFACE))) ||
                    ((nextKey instanceof Fake) && (((Fake)nextKey).isInterface())))
                { // inteface
                    // we want to have interfaces first
                    position = 0;
                }
                else
                { // class
                    // classes after the interfaces
                    position = vectorWithInterfacesFirst.size();
                }
                DefaultMutableTreeNode newNode = null;
                Object value = table.get(nextKey);
                if (value instanceof Hashtable)
                {
                    newNode =
                        createParentForChildren(nextKey, getNodesForKeysOfHT((Hashtable)value));
                }
                else
                {
                    newNode = new DefaultMutableTreeNode(nextKey);
                }
                vectorWithInterfacesFirst.insertElementAt(newNode, position);
            }
            return vectorWithInterfacesFirst;
        }
    }

    /**
     * A simple tree renderer class.
     * Displays an entry in a tree.
     * @see javax.swing specification
     */
    private class TreeRend extends DefaultTreeCellRenderer
    {
        public Component getTreeCellRendererComponent
        (JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus)
        {
            Object foundObject = ((DefaultMutableTreeNode)value).getUserObject();
            if (foundObject instanceof SciClass)
            {
                SciClass classOrInterface = (SciClass)foundObject;
                if (classOrInterface.isDeleted())
                {
                    setIcon(null);
                    setText("The class or interface is deleted.");
                    // ((DefaultTreeModel)tree.getModel()).reload((DefaultMutableTreeNode)value);
                }
                else
                {
                    setText(classOrInterface.getQualifiedName());
                    if (classOrInterface.hasProperty(SciProperty.INTERFACE))
                    {
                        setIcon(interfaceIcon);
                    }
                    else
                    {
                        setIcon(classIcon);
                    }
                }
            }
            else if (foundObject instanceof Fake)
            {
                setText(((Fake)foundObject).getDescription());
                setIcon(((Fake)foundObject).getIcon());
            }
            return this;
        }
    }

    /**
     * This class keeps Hashtables of Hashtables representing the model's data.
     * This hierarchy: A--| B | C--| D is represented by these hashtables:
     * (A -> (  B -> empty HT C -> ( D -> empty HT)  )  )
     * The processedHT is a HT which keeps a vector with hierarchies (HTs),
     * where the specified Object is a parent.
     * For example, the "A" object might be a parent of two hierarchies -
     * the one above and A--| E. It is represented by this hashtable:
     * (A-> ( E - > empty HT) )
     * We don't join these hierarchies, because they may be used in different places
     * separately.
     * For the object representing "A" the processedHT will contain a vector with
     * two HTs shown above. Now, when you add some object to the existing hierarchies
     * of parent object (for our example, when you add an object "X" to the
     * hierarchies of "A"), all corresponding HTs are populated with a
     * new "X -> new empty Hashtable" pair. The method putOneUnderAnother allows
     * you to do this. Moreover, you don't care about the order of populating
     * the tree. That is you can add a child first, populate it with some other
     * children, and after that if you invoke putOneUnderAnother for some parent
     * and this existing child it will correctly link their hierarchies. So far,
     * the Hashtable for a fake "root" object represents a tree.
     * Why not to use TreeNodes? Because the we don't have to create a new node
     * each time we work with children (cause by default a node can be inserted
     * in only one parent... clone creates a shallow copy ...)
     * At last, although processedHT has a very complicated structure, it uses
     * the same amount of objects that the tree has (well, almost the same :))
     * and it is quick in inserting nodes into the middle of the tree.
     */
    private class DataStorage
    {
        /*
         * Returns tree data hashtable.
         */
        public Hashtable getTreeData()
        {
            // if the data model for the tree is not empty
            if (processedHT.get(root) != null)
            {
                return (Hashtable)((Vector)processedHT.get(root)).elementAt(0);
            }
            return new Hashtable(); // empty tree
        }

        /*
         * Puts specified object to the tree top.
         */
        public void putToTheTop(Object whatToPut)
        {
            putOneUnderAnother(whatToPut, root);
        }

        /*
         * Returns flag, if object is on the tree top.
         */
        public boolean isOnTheTop(Object what)
        {
            return getTreeData().containsKey(what);
        }

        /*
         * Removes object from the tree top.
         */
        public void removeFromTheTop(Object what)
        {
            getTreeData().remove(what);
        }

        /*
         * Puts one object under another in tree.
         */
        public void putOneUnderAnother(Object whatToPut, Object parentObject)
        {
            if (!processedHT.containsKey(parentObject))
            {
                Vector newVector = new Vector();
                newVector.addElement(new Hashtable());
                processedHT.put(parentObject, newVector);
            }
            if (whatToPut == null)
            {
                return;
            }
            Vector occurences = (Vector)processedHT.get(parentObject);
            if (processedHT.containsKey(whatToPut))
            {
                // we have already hierarchies for "wahtToPut"
                // and now we must insert them into "parentObject" occurences
                final int occurencesSize = occurences.size();
                for (int i = 0; i < occurencesSize; i++)
                {
                    Hashtable nextOccurenceHT = (Hashtable)occurences.elementAt(i);
                    Vector existingOccurrencesOfWhatToPut = (Vector)processedHT.get(whatToPut);
                    final int existingOccurrencesOfWhatToPutSize = existingOccurrencesOfWhatToPut.size();
                    for (int j = 0; j < existingOccurrencesOfWhatToPutSize; j++)
                    {
                        nextOccurenceHT.put
                        (whatToPut, (Hashtable)existingOccurrencesOfWhatToPut.elementAt(j));
                    }
                }
            }
            else
            { // this is the first time we are inserting "wantToPut"
                final int occurencesSize = occurences.size();
                for (int i = 0; i < occurencesSize; i++)
                {
                    Hashtable nextOccurenceHT = (Hashtable)occurences.elementAt(i);
                    Hashtable newLeaf = new Hashtable();
                    nextOccurenceHT.put(whatToPut, newLeaf);
                    Vector newData = (Vector)processedHT.get(whatToPut);
                    if (newData == null)
                    {
                        newData = new Vector();
                    }
                    newData.addElement(newLeaf);
                    processedHT.put(whatToPut, newData);
                }
            }
        }

        // processed hashtable
        private Hashtable processedHT = new Hashtable();

        // all the tree data will be kept under this root
        private Object root = new Object();
    }

    /**
     * This class is used to create nodes representing non-existing SciClasses.
     */
    private class Fake
    {
        /*
         * Cretaes class instance with specified parameters.
         */
        public Fake(String description, boolean isInterfaceFlag)
        {
            myDescription = description;
            myIcon = (isInterfaceFlag) ? interfaceIcon : classIcon;
            myIsInterface = isInterfaceFlag;
        }

        /*
         * Returns Icon object.
         */
        public Icon getIcon()
        {
            return myIcon;
        }

        /*
         * Returns string description.
         */
        public String getDescription()
        {
            return myDescription;
        }

        /*
         * Returns flag, if object is interface or not.
         */
        public boolean isInterface()
        {
            return myIsInterface;
        }

        private Icon myIcon;           // icon
        private String myDescription;  // description
        private boolean myIsInterface; // it's interface or not
    }

}


