Package org.openquark.gems.client.browser

Source Code of org.openquark.gems.client.browser.BrowserTreeModel$MetaModuleListFilter

/*
* Copyright (c) 2007 BUSINESS OBJECTS SOFTWARE LIMITED
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
*     * Redistributions of source code must retain the above copyright notice,
*       this list of conditions and the following disclaimer.
*     * Redistributions in binary form must reproduce the above copyright
*       notice, this list of conditions and the following disclaimer in the
*       documentation and/or other materials provided with the distribution.
*     * Neither the name of Business Objects nor the names of its contributors
*       may be used to endorse or promote products derived from this software
*       without specific prior written permission.
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/


/*
* BrowserTreeModel.java
* Creation date: (9/14/00 10:58:58 AM)
* By: Luke Evans
*/
package org.openquark.gems.client.browser;

import java.awt.EventQueue;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.regex.Pattern;

import javax.swing.SwingUtilities;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeNode;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.Predicate;
import org.openquark.cal.compiler.ModuleName;
import org.openquark.cal.compiler.ModuleNameResolver;
import org.openquark.cal.compiler.ModuleTypeInfo;
import org.openquark.cal.compiler.ScopedEntityNamingPolicy;
import org.openquark.cal.compiler.ScopedEntityNamingPolicy.UnqualifiedUnlessAmbiguous;
import org.openquark.cal.services.GemComparatorSorter;
import org.openquark.cal.services.GemEntity;
import org.openquark.cal.services.GemFormSorter;
import org.openquark.cal.services.GemSorter;
import org.openquark.cal.services.GemUnqualifiedNameCaseInsensitiveSorter;
import org.openquark.cal.services.MetaModule;
import org.openquark.cal.services.Perspective;
import org.openquark.gems.client.FeatureVisibilityPolicy;
import org.openquark.gems.client.ModuleNameDisplayUtilities;
import org.openquark.gems.client.ModuleNameDisplayUtilities.TreeViewDisplayMode;
import org.openquark.gems.client.browser.BrowserTreeNode.NodeReuseInfo;
import org.openquark.gems.client.browser.GemCategory.CategoryKey;
import org.openquark.util.UnsafeCast;
import org.openquark.util.WildcardPatternMatcher;


/**
* The model for the browser tree displayed in the outline control.
* This model contains the abstract tree model and has various methods to construct the displayed tree.
*
* @author Luke Evans
*/
public class BrowserTreeModel extends DefaultTreeModel {
    private static final long serialVersionUID = 2746652802349469424L;

    /** Refers to the root of the abstract tree. */
    private final RootAbstractTreeNode workspaceAbstractTreeNode;

    /** Refers to the tree node for the workspace. */
    private final WorkspaceRootTreeNode workspaceTreeNode = new WorkspaceRootTreeNode(BrowserMessages.getString("GB_WorkspaceNodeName"));

    /** Refers to the tree node that holds the search results. */
    private final SearchResultRootTreeNode searchResultsTreeNode = new SearchResultRootTreeNode ();

    /**
     * The last search string that was used to search the model. Reload uses this to
     * restore the model state if it is reloaded.
     */
    private String lastSearchString = null;

    /** a map from an entity to a corresponding GemTreeNode.
     * This is used for updating the UI when we want to open "the" node for an entity.
     * There may be more than one, but it doesn't matter which one we pick, so this
     * map stores only one of the possibilities. */
    private final Map<GemEntity, GemTreeNode> entityToTreeNodeMap;

    /** The perspective which this model represents (if any). */
    private Perspective perspective = null;
   
    /** If true unimported modules will be shown. */
    private boolean showAllModules = true;
   
    /** If true show only public gems. default is to show all gems*/
    private boolean showPublicGemsOnly = false;
  
    /** This predicate decides if a CAL feature is visible to the user */
    private FeatureVisibilityPolicy visibilityPolicy;
   
    /** This module name resolver maintains the module name resolution rules for the currently displayed set of modules. */
    private ModuleNameResolver workspaceModuleNameResolver = ModuleNameResolver.make(Collections.<ModuleName>emptySet());
   
    static final class GemDrawerCategoryInfo {
        private final ModuleName moduleName;
        private final boolean isNamespaceNode;
       
        /**
         * @param moduleName
         * @param isNamespaceNode
         */
        GemDrawerCategoryInfo(final ModuleName moduleName, final boolean isNamespaceNode) {
            this.moduleName = moduleName;
            this.isNamespaceNode = isNamespaceNode;
        }

        ModuleName getModuleName() {
            return moduleName;
        }

        boolean isNamespaceNode() {
            return isNamespaceNode;
        }
       
        @Override
        public boolean equals(Object other) {
            if (other instanceof GemDrawerCategoryInfo) {
                return equals((GemDrawerCategoryInfo)other);
            } else {
                return false;
            }
        }
       
        public boolean equals(GemDrawerCategoryInfo other) {
            return this.moduleName.equals(other.moduleName) && this.isNamespaceNode == other.isNamespaceNode;
        }
       
        @Override
        public int hashCode() {
            return 37 * moduleName.hashCode() + Boolean.valueOf(isNamespaceNode).hashCode();
        }
    }
   
    private final class MaterialGemDrawerCategoryNodeProvider implements CategoryNodeProvider {
       
        private final TreeViewDisplayMode moduleTreeDisplayMode;
       
        private MaterialGemDrawerCategoryNodeProvider(TreeViewDisplayMode moduleTreeDisplayMode) {
            if (moduleTreeDisplayMode == null) {
                throw new NullPointerException();
            }
            this.moduleTreeDisplayMode = moduleTreeDisplayMode;
        }
       
        private TreeViewDisplayMode getModuleTreeDisplayMode() {
            return moduleTreeDisplayMode;
        }
       
        public BrowserTreeNode addCategoryNodeToParent(final BrowserTreeNode parent, final GemCategory category, final NodeReuseInfo nodeReuseInfo) {
           
            final GemDrawerCategoryInfo categoryInfo = (GemDrawerCategoryInfo)(category.getCategoryKey()).getValue();
            final ModuleName moduleName = categoryInfo.getModuleName();
           
            if (nodeReuseInfo != null) {
                final GemDrawer reusedGemDrawer = nodeReuseInfo.getDescendantGemDrawer(moduleName);
               
                // the namespace node state must match before the node can be reused
                if (reusedGemDrawer != null && !reusedGemDrawer.isNamespaceNode()) {
                   
                    if (getModuleTreeDisplayMode() != TreeViewDisplayMode.HIERARCHICAL) {
                        return parent.addCategoryNode(reusedGemDrawer, category);
                       
                    } else {
                        final int nComponents = moduleName.getNComponents();
                       
                        BrowserTreeNode parentNode = parent;
                       
                        for (int i = 0; i < nComponents; i++) {
                            final ModuleName prefix = moduleName.getPrefix(i + 1); // goes from 1 to nComponents through the loop iterations
                            final boolean isIntermediateNamespaceNode = i != nComponents - 1;
                            final GemCategory prefixCategory = new GemCategory(new GemCategory.CategoryKey<GemDrawerCategoryInfo>(new GemDrawerCategoryInfo(prefix, isIntermediateNamespaceNode)));
                           
                            final GemDrawer gemDrawer;
                            if (!isIntermediateNamespaceNode) {
                                // for the final node in the sequence, use the reused node
                                gemDrawer = reusedGemDrawer;
                            } else {
                                gemDrawer = new MaterialGemDrawer(prefix, workspaceModuleNameResolver, getModuleTreeDisplayMode(), true);
                            }

                            // prefixNode may be different from materialGemDrawer - if the parent already has a node of the same category
                            final BrowserTreeNode prefixNode = parentNode.addCategoryNode(gemDrawer, prefixCategory);
                           
                            // for the next loop iteration:
                            parentNode = prefixNode;
                        }
                       
                        return parentNode;
                       
                    }
                }
            }
           
            if (getModuleTreeDisplayMode() != TreeViewDisplayMode.HIERARCHICAL) {
                return parent.addCategoryNode(new MaterialGemDrawer(moduleName, workspaceModuleNameResolver, getModuleTreeDisplayMode(), false), category);
               
            } else {
                final int nComponents = moduleName.getNComponents();
               
                BrowserTreeNode parentNode = parent;
               
                for (int i = 0; i < nComponents; i++) {
                    final ModuleName prefix = moduleName.getPrefix(i + 1); // goes from 1 to nComponents through the loop iterations
                    final boolean isIntermediateNamespaceNode = i != nComponents - 1;
                    final GemCategory prefixCategory = new GemCategory(new GemCategory.CategoryKey<GemDrawerCategoryInfo>(new GemDrawerCategoryInfo(prefix, isIntermediateNamespaceNode)));
                   
                    final MaterialGemDrawer materialGemDrawer = new MaterialGemDrawer(prefix, workspaceModuleNameResolver, getModuleTreeDisplayMode(), isIntermediateNamespaceNode);

                    // prefixNode may be different from materialGemDrawer - if the parent already has a node of the same category
                    final BrowserTreeNode prefixNode = parentNode.addCategoryNode(materialGemDrawer, prefixCategory);
                   
                    // for the next loop iteration:
                    parentNode = prefixNode;
                }
               
                return parentNode;
            }
        }
    }

    /**
     * The abstract super class for nodes in the abstract tree.
     * The abstract tree has three levels.
     * 1) The root workspace level  (the root of the tree).
     * 2) The Drawer level (corresponding to modules).
     * 3) The Entity level
     *
     * It is possible to get the entire subtree rooted at the node that you are looking
     * at simply by using its children list, and its children's children list...etc.
     */
    private static abstract class ModelAbstractTreeNode {

        /** The name of this node */
        private String name;
       
        /**
         * Constructor for a ModelAbstractTreeNode
         * @param nodeName the name of the node
         */
        public ModelAbstractTreeNode(String nodeName) {
            this.name = nodeName;
        }

        public String getName() {
            return name;
        }
       
        public void setName(String nodeName) {
            name = nodeName;
        }
    }
   
    /**
     * A model tree node that can have children.
     * @author Edward Lam
     */
    private static class AbstractParentTreeNode extends ModelAbstractTreeNode {

        /** The children of this node. */
        private final List<ModelAbstractTreeNode> childrenList;
       
        /** Map from child node name to the child node. */
        private final Map<String, ModelAbstractTreeNode> childMap;
       
        public AbstractParentTreeNode(String viewName) {
            super(viewName);

            childrenList = new ArrayList<ModelAbstractTreeNode>();
            childMap = new HashMap<String, ModelAbstractTreeNode>();
        }

        /**
         * Add a child to this abstract tree node.
         * @param childNode ModelAbstractTreeNode the child node to add.
         * @return boolean whether the add was successful.
         */   
        public boolean addChild(ModelAbstractTreeNode childNode) {
            childMap.put(childNode.getName(), childNode);
            return childrenList.add(childNode);
        }

        /**
         * Get a child of this node by name.
         * @param childName String the name of the child to retrieve
         * @return ModelAbstractTreeNode the child node with the given name
         */
        public ModelAbstractTreeNode getChild(String childName) {
            return childMap.get(childName);
        }
       
        /**
         * Returns whether there is a child whose name is prefixed by the given prefix.
         * @param namePrefix
         * @return true if there is a child whose name is prefixed by the given prefix.
         */
        public boolean hasChildWithPrefix(final String namePrefix) {
            for (final String childName : childMap.keySet()) {
                if (childName.startsWith(namePrefix)) {
                    return true;
                }
            }
            return false;
        }

        /**
         * Get the children of this node.
         * @return List the children of the node.
         */
        public List<ModelAbstractTreeNode> getChildrenList() {
            return (childrenList != null) ? new ArrayList<ModelAbstractTreeNode>(childrenList) : new ArrayList<ModelAbstractTreeNode>();
        }

        /**
         * Clear all children.  Needed for hack GemCutter reentrancy.
         * Only needed by ViewAbstractTreeNode in order to clear all drawers..
         */
        void clearChildren() {
            childMap.clear();
            childrenList.clear();
        }
       
        /**
         * Compare two children by their order in the abstract tree..
         * @param child1 the first child to compare
         * @param child2 the second child to compare
         * @return int the result of the comparison.
         */
        int compareOrder(ModelAbstractTreeNode child1, ModelAbstractTreeNode child2) {

            if (child1 == null || child2 == null) {
                throw new IllegalArgumentException("Attempt to compare nodes which are null.");
            }

            if (child1 == child2) {
                return 0;
            }

            // Get the children's relative order
            int index1 = childrenList.indexOf(child1);
            int index2 = childrenList.indexOf(child2);
           
            if (index1 < 0 || index2 < 0) {
                throw new IllegalArgumentException("Attempt to compare nodes which are not children.");
            }
           
            return index1 - index2;
        }

        /**
         * Compare two children by their order in the abstract tree..
         * @param childName1 the name of the first child to compare
         * @param childName2 the name of the second child to compare
         * @return int the result of the comparison.
         */
        int compareOrder(String childName1, String childName2) {
            return compareOrder(getChild(childName1), getChild(childName2));
        }
    }
   
    /**
     * The abstract tree node for the root.
     * There exists only one root for the tree.
     */
    private static class RootAbstractTreeNode extends AbstractParentTreeNode {
        public RootAbstractTreeNode(String nodeName) {
            super(nodeName);
        }
    }
   
    /**
     * The abstract GemDrawer tree node.
     * A GemDrawer is a module (or possibly some other source of entities).
     */
    private static abstract class GemDrawerAbstractTreeNode extends AbstractParentTreeNode {
        public GemDrawerAbstractTreeNode(String gemDrawerName) {
            super(gemDrawerName);
        }
    }
   
    /**
     * The abstract MaterialGemDrawer tree node.
     * It represents material (compiled) modules and entities.
     */
    private static class MaterialGemDrawerAbstractTreeNode extends GemDrawerAbstractTreeNode {
        public MaterialGemDrawerAbstractTreeNode(ModuleName moduleName) {
            super(getGemDrawerNameForModule(moduleName));     
        }
    }
   
    /**
     * The model tree node that holds an entity.
     * @author Edward Lam
     */
    private static class EntityAbstractTreeNode extends ModelAbstractTreeNode
        private final GemEntity gemEntity;
   
        public EntityAbstractTreeNode(GemEntity gemEntity) {
            super(gemEntity.getName().getUnqualifiedName());
            this.gemEntity = gemEntity;
        }  
   
        public GemEntity getEntity() {
            return gemEntity;
        }  
    }

    /**
     * Get a comparator on entities according to the name of the gem drawer they are in.
     * The primary key is whether the drawer actually exists.
     * The secondary key is the name of the drawer.
     * @author Edward Lam
     */
    private class GemDrawerNameComparator implements Comparator<GemCategory.CategoryKey<GemDrawerCategoryInfo>> {
       
        private final TreeViewDisplayMode moduleTreeDisplayMode;
       
        private GemDrawerNameComparator(final TreeViewDisplayMode moduleTreeDisplayMode) {
            if (moduleTreeDisplayMode == null) {
                throw new NullPointerException();
            }
            this.moduleTreeDisplayMode = moduleTreeDisplayMode;
        }
       
        private TreeViewDisplayMode getModuleTreeDisplayMode() {
            return moduleTreeDisplayMode;
        }
       
        public int compare(final GemCategory.CategoryKey<GemDrawerCategoryInfo> e1, final GemCategory.CategoryKey<GemDrawerCategoryInfo> e2) {

            final GemDrawerCategoryInfo categoryInfo1 = e1.getValue();
            final GemDrawerCategoryInfo categoryInfo2 = e2.getValue();

            final ModuleName moduleName1 = categoryInfo1.getModuleName();
            final ModuleName moduleName2 = categoryInfo2.getModuleName();
           
            final boolean isNamespaceNode1 = categoryInfo1.isNamespaceNode();
            final boolean isNamespaceNode2 = categoryInfo2.isNamespaceNode();
           
            return compareDrawers(moduleName1, isNamespaceNode1, moduleName2, isNamespaceNode2);           
        }

        /**
         * Compares the 2 given drawers by their names and by whether they are namespace nodes or not.
         * @param moduleName1
         * @param isNamespaceNode1
         * @param moduleName2
         * @param isNamespaceNode2
         */
        private int compareDrawers(final ModuleName moduleName1, final boolean isNamespaceNode1, final ModuleName moduleName2, final boolean isNamespaceNode2) {
           
            final boolean isVisible1 = isNamespaceNode1 ? doesNamespaceContainVisibleModules(moduleName1) : isVisibleModule(moduleName1);
            final boolean isVisible2 = isNamespaceNode2 ? doesNamespaceContainVisibleModules(moduleName2) : isVisibleModule(moduleName2);
           
            if (getModuleTreeDisplayMode() == TreeViewDisplayMode.HIERARCHICAL) {
               
                final ModuleName commonPrefix = moduleName1.getCommonPrefix(moduleName2);
                final int commonPrefixLength = (commonPrefix == null) ? 0 : commonPrefix.getNComponents();
               
                final int moduleNameLength1 = moduleName1.getNComponents();
                final int moduleNameLength2 = moduleName2.getNComponents();
               
                if (commonPrefixLength == moduleNameLength1) {
                    if (commonPrefixLength == moduleNameLength2) {
                        // the module names are equal, so the namespace node comes after the module node
                        if (isNamespaceNode1) {
                            if (isNamespaceNode2) {
                                return 0;
                            } else {
                                return 1;
                            }
                        } else if (isNamespaceNode2) {
                            return -1;
                        } else {
                            return 0;
                        }
                    } else {
                        // moduleName1 is a prefix of moduleName2, so moduleName2 comes after because its namespace node
                        // which shares the name of moduleName1 comes after
                        return -1;
                    }
                   
                } else if (commonPrefixLength == moduleNameLength2) {
                    // moduleName2 is a prefix of moduleName1, so moduleName1 comes after because its namespace node
                    // which shares the name of moduleName2 comes after
                    return 1;
                   
                } else {
                    // neither module name is a prefix of the other, so order by the immediate children of the common prefix
                    final ModuleName shortestDisambiguator1 = moduleName1.getPrefix(commonPrefixLength + 1);
                    final ModuleName shortestDisambiguator2 = moduleName2.getPrefix(commonPrefixLength + 1);
                   
                    final boolean shortestDisambiguatorIsNamespaceNode1 =
                        (shortestDisambiguator1.getNComponents() == moduleName1.getNComponents()) ? isNamespaceNode1 : true;
                   
                    final boolean shortestDisambiguatorIsNamespaceNode2 =
                        (shortestDisambiguator2.getNComponents() == moduleName2.getNComponents()) ? isNamespaceNode2 : true;
                   
                    final boolean shortestDisambiguatorIsVisible1 =
                        shortestDisambiguatorIsNamespaceNode1 ? doesNamespaceContainVisibleModules(shortestDisambiguator1) : isVisibleModule(shortestDisambiguator1);
                       
                    final boolean shortestDisambiguatorIsVisible2 =
                        shortestDisambiguatorIsNamespaceNode2 ? doesNamespaceContainVisibleModules(shortestDisambiguator2) : isVisibleModule(shortestDisambiguator2);
                   
                    // Note that drawers which are visible should always come before those that aren't.
                    if (shortestDisambiguatorIsVisible1 && !shortestDisambiguatorIsVisible2) {
                        return -1;
                   
                    } else if (!shortestDisambiguatorIsVisible1 && shortestDisambiguatorIsVisible2) {
                        return 1;
                   
                    } else {
                        // These drawers either are both visible, or both invisible... in both cases they are treated in a similar fashion
                        return shortestDisambiguator1.compareTo(shortestDisambiguator2);
                    }
                }
               
            } else if (getModuleTreeDisplayMode() == TreeViewDisplayMode.FLAT_FULLY_QUALIFIED) {
               
                // Note that drawers which are visible should always come before those that aren't.
                if (isVisible1 && !isVisible2) {
                    return -1;
               
                } else if (!isVisible1 && isVisible2) {
                    return 1;
               
                } else {
                    // These drawers either are both visible, or both invisible... in both cases they are treated in a similar fashion
                    return moduleName1.compareTo(moduleName2);
                }
               
            } else if (getModuleTreeDisplayMode() == TreeViewDisplayMode.FLAT_ABBREVIATED) {
               
                // Note that drawers which are visible should always come before those that aren't.
                if (isVisible1 && !isVisible2) {
                    return -1;
               
                } else if (!isVisible1 && isVisible2) {
                    return 1;
               
                } else {
                    // These drawers either are both visible, or both invisible... in both cases they are treated in a similar fashion
                    final int nameBasedResult = moduleName1.getLastComponent().compareTo(moduleName2.getLastComponent());
                    if (nameBasedResult != 0) {
                        return nameBasedResult;
                    } else {
                        // if the last components are the same (and thus the names are abbreviated to the same name), they
                        // must be distinguished by their immediate prefixes.
                        final ModuleName immediatePrefix1 = moduleName1.getImmediatePrefix();
                        final ModuleName immediatePrefix2 = moduleName2.getImmediatePrefix();
                       
                        if (immediatePrefix1 == null) {
                            if (immediatePrefix2 == null) {
                                return 0;
                            } else {
                                return -1;
                            }
                        } else if (immediatePrefix2 == null) {
                            return 1;
                        } else {
                            return immediatePrefix1.compareTo(immediatePrefix2);   
                        }
                    }
                }
               
            } else {
                throw new IllegalStateException("Unexpected TreeViewDisplayMode: " + getModuleTreeDisplayMode());
            }
        }
    }

    /**
     * A category node provider provides a tree node for a given category.
     * @author Edward Lam
     */
    interface CategoryNodeProvider {
        /**
         * Get the tree node appropriate to hold nodes in a given category.
         * @param parent the parent to which the node is to be added.
         * @param category the category
         * @param nodeReuseInfo the node reuse info
         * @return BrowserTreeNode the node that ends up as a child of the parent.
         */
        public BrowserTreeNode addCategoryNodeToParent(BrowserTreeNode parent, GemCategory category, NodeReuseInfo nodeReuseInfo);
    }

    /**
     * The default category node provider just returns an empty default mutable tree node.
     * @author Edward Lam
     */
    public static class DefaultCategoryNodeProvider implements CategoryNodeProvider {

        /** {@inheritDoc} */
        public BrowserTreeNode addCategoryNodeToParent(final BrowserTreeNode parent, final GemCategory category, final NodeReuseInfo nodeReuseInfo) {
            if (nodeReuseInfo == null) {
                return parent.addCategoryNode(new GemCategoryNode(category.getCategoryKey()), category);
            } else {
                final BrowserTreeNode reusedNode = nodeReuseInfo.getChildNodeFromDisplayedString(category.getCategoryKey().getName(), GemCategoryNode.class);
                if (reusedNode != null) {
                    return parent.addCategoryNode(reusedNode, category);
                } else {
                    return parent.addCategoryNode(new GemCategoryNode(category.getCategoryKey()), category);
                }
            }
        }
    }

    /**
     * The special implementation of the org.apache.commons.collections
     * <code>Predicate</code> interface that filters out modules that fail
     * the visibility check.
     */   
    private class MetaModuleListFilter implements Predicate {
        /** @see org.apache.commons.collections.Predicate#evaluate(java.lang.Object) */
        public boolean evaluate(Object object) {
            if (object instanceof MetaModule) {
                return visibilityPolicy.isModuleVisible((MetaModule) object);
            }
            return false;
        }
    }

    /**
     * A GemCategorizer which categorizes by a GemEntity's module name.
     * @author Edward Lam
     */
    private class ModuleCategorizer extends GemCategorizer<GemDrawerCategoryInfo> {
       
        private final GemDrawerNameComparator drawerComparator;
       
        private final TreeViewDisplayMode moduleTreeDisplayMode;

        /**
         * Constructor for a VaultModel.ModuleCategorizer.
         */
        public ModuleCategorizer(final List<GemDrawerCategoryInfo> categoriesToInclude, final boolean formNewCategories, final TreeViewDisplayMode moduleTreeDisplayMode) {
            super(categoriesToInclude, formNewCategories);
            if (moduleTreeDisplayMode == null) {
                throw new NullPointerException();
            }
            this.drawerComparator = new GemDrawerNameComparator(moduleTreeDisplayMode);
            this.moduleTreeDisplayMode = moduleTreeDisplayMode;
        }
       
        TreeViewDisplayMode getModuleTreeDisplayMode() {
            return moduleTreeDisplayMode;
        }
       
        @Override
        public List<GemCategory.CategoryKey<GemDrawerCategoryInfo>> getCategoryKeyList(GemEntity gemEntity) {
            final ModuleName moduleName = gemEntity.getName().getModuleName();
            return Collections.singletonList(new GemCategory.CategoryKey<GemDrawerCategoryInfo>(new GemDrawerCategoryInfo(moduleName, false)));
        }

        @Override
        public int compareKey(GemCategory.CategoryKey<GemDrawerCategoryInfo> key1, GemCategory.CategoryKey<GemDrawerCategoryInfo> key2) {
            return drawerComparator.compare(key1, key2);
        }
    }
   
    /**
     * Default constructor for BrowserTreeModel.
     * Automatically constructs a root node in the abstract tree and display
     * all modules/gems found in the workspace.
     */
    public BrowserTreeModel() {
        // The main root node will not be visible
        super(new BrowserTreeRootNode(), true);

        // Add the search results and workspace nodes
        ((BrowserTreeNode) getRoot()).add(workspaceTreeNode);
        ((BrowserTreeNode) getRoot()).add(searchResultsTreeNode);
       
        // Create a new root abstract tree node.
        workspaceAbstractTreeNode = new RootAbstractTreeNode(workspaceTreeNode.getName());

        // Use a weak hashmap for the entities so that GemEntities which are no longer
        // loaded are automatically cleared from the map.
        entityToTreeNodeMap = new WeakHashMap<GemEntity, GemTreeNode>();

        // visibility predicate can never be null
        visibilityPolicy = FeatureVisibilityPolicy.getDefault();
    }  
   
    /**
     * Returns the visibility policy used for this browser tree model.  The return value can never be null.
     * @return FeatureVisibilityPolicy
     */
    public FeatureVisibilityPolicy getFeatureVisibilityPolicy() {
        return visibilityPolicy;
    }

    /**
     * Sets the visibility policy used for this browser tree model.  A visibility policy is a predicate
     * function that determines in a module or a gem is visible in the browser tree.  If
     * <code>null</code> is used as an argument, then the default visibility predicate is used.
     * @param predicate
     */
    public void setFeatureVisibilityPolicy(FeatureVisibilityPolicy predicate) {
        if (predicate == null) {
            visibilityPolicy = FeatureVisibilityPolicy.getDefault();
        } else {
            visibilityPolicy = predicate;
        }
    }

    /**
     * Return whether a given module is visible from the perspective from which the tree was last populated.
     * @param moduleName
     * @return whether the module is visible from the perspective from which the tree was last populated.
     */
    public boolean isVisibleModule(ModuleName moduleName) {
        if (perspective == null) {
            return false;
        }
        return perspective.isVisibleModule(moduleName);
    }
   
    /**
     * Return whether a given namespace contains modules visible from the perspective from which the tree was last populated.
     * @param namespaceName
     * @return whether the namespace contains modules visible from the perspective from which the tree was last populated.
     */
    public boolean doesNamespaceContainVisibleModules(ModuleName namespaceName) {
        if (perspective == null) {
            return false;
        }
       
        final MetaModule workingModule = perspective.getWorkingModule();
        if (workingModule == null) {
            return false;
        }
       
        if (namespaceName.isProperPrefixOf(workingModule.getName())) {
            return true;
        }
       
        final ModuleTypeInfo moduleTypeInfo = workingModule.getTypeInfo();
        final int n = moduleTypeInfo.getNImportedModules();
        for (int i = 0; i < n; i++) {
            if (namespaceName.isProperPrefixOf(moduleTypeInfo.getNthImportedModule(i).getModuleName())) {
                return true;
            }
        }
       
        return false;
    }
   
    /**
     * Return whether a given gem is visible from the perspective from which the tree was last populated.
     * @param gemEntity
     * @return whether the gem is visible from the perspective from which the tree was last populated.
     */
    public boolean isVisibleGem(GemEntity gemEntity) {
        ModuleName moduleName = gemEntity.getName().getModuleName();
        return isVisibleModule(moduleName);
    }
   
    /**
     * Return whether a given module is the working module (according to the perspective when the tree was last populated).
     * @param moduleName the name of the module.
     * @return whether the name is the name of the working module.
     */
    boolean isWorkingModule(ModuleName moduleName) {
        if (perspective == null) {
            return false;
        }
        ModuleName workingModuleName = perspective.getWorkingModuleName();
        return workingModuleName.equals(moduleName);
    }

    /**
     * Get a GemTreeNode corresponding to a given entity.
     * This is usually, but NOT ALWAYS, the only node that corresponds to the entity.
     * Some entities will have multiple associated tree nodes; this function returns one
     * chosen arbitrarily.
     * @param newGemEntity the entity to look up
     * @return GemTreeNode the corresponding node.
     */
    public GemTreeNode getTreeNode(GemEntity newGemEntity) {
        return entityToTreeNodeMap.get(newGemEntity);
    }

    /**
     * Returns all the entities in the leaf nodes (GemTreeNodes) descended from the specified tree node.
     * @param treeNode the tree node to analyze.
     * @return List of all entities in GemTreeNodes descended from the current node.
     */
    private List<GemEntity> getAllEntities(BrowserTreeNode treeNode) {
        Set<GemEntity> entitySet = new HashSet<GemEntity>();
        getAllEntitiesHelper(treeNode, entitySet);
        return new ArrayList<GemEntity>(entitySet);
    }
   
    /**
     * Adds all the entities in the leaf nodes (GemTreeNodes) descended from the specified tree node to a
     * provided Set.
     * @param treeNode the tree node to analyze
     * @param entitySet the Set to add the descended entities to
     */
    private void getAllEntitiesHelper(BrowserTreeNode treeNode, Set<GemEntity> entitySet) {   

        if (treeNode instanceof GemTreeNode) {
            entitySet.add((GemEntity)treeNode.getUserObject());
            return;
        }

        int numChildren = treeNode.getChildCount();
        for (int i = 0; i < numChildren; i++) {
            BrowserTreeNode childNode = (BrowserTreeNode)treeNode.getChildAt(i);

            if (childNode instanceof GemTreeNode) {
                entitySet.add((GemEntity)childNode.getUserObject());

            } else {
                // must be a node that holds other nodes
                getAllEntitiesHelper(childNode, entitySet);
            }
        }
    }
   

    /**
     * Categorize all entities descending from this node according to a given categorizer.
     * @param selectedNode The node below which all entities will be categorized.
     * @param categorizer The categorizer to use to categorize the gementities.
     */
    private void categorizeByX(BrowserTreeNode selectedNode, GemCategorizer<?> categorizer) {
        categorizeByX(selectedNode, categorizer, new DefaultCategoryNodeProvider());
    }

    /**
     * Categorize all entities descending from this node according to a given categorizer, and producing category
     * nodes according to a given category node provider.
     * @param selectedNode The node below which all entities will be categorized.
     * @param categorizer The categorizer to use to categorize the gementities.
     * @param provider The CategoryNodeProvider to provide custom nodes to represent the categories.
     */
    private void categorizeByX(BrowserTreeNode selectedNode, GemCategorizer<?> categorizer, CategoryNodeProvider provider) {
        categorizeByX(selectedNode, categorizer, provider, false);
    }

    /**
     * Categorize all entities descending from this node according to a given categorizer, and producing category
     * nodes according to a given category node provider.
     * @param selectedNode The node below which all entities will be categorized.
     * @param categorizer The categorizer to use to categorize the gementities.
     * @param provider The CategoryNodeProvider to provide custom nodes to represent the categories.
     * @param reuseNodes whether to try to reuse child nodes when re-categorizing a given node.
     */
    private void categorizeByX(final BrowserTreeNode selectedNode, GemCategorizer<?> categorizer, CategoryNodeProvider provider, boolean reuseNodes) {

        ModuleTypeInfo workingModuleTypeInfo = perspective.getWorkingModuleTypeInfo();
       
        // Before categorizing sort gems alphabetically so they appear
        // in alphabetical order inside their categories.
        GemComparatorSorter sorter = new GemUnqualifiedNameCaseInsensitiveSorter();
        List<GemEntity> childList = sorter.getSortedList(getAllEntities(selectedNode));

        // Attach the categorizer info to this node.
        selectedNode.setCategorizer(categorizer, provider);

        // Categorize the children list.
        List<GemCategory> categories = categorizer.formCategories(childList);
       
        // Construct the map for reuse of nodes.  Populate if necessary.
        BrowserTreeNode.NodeReuseInfo nodeReuseInfo = reuseNodes ? selectedNode.getChildNameInfo() : null;
       
        if (selectedNode instanceof GemDrawer) {
            // Now, remove all the children of the selected node, and re-insert them into categories.
            selectedNode.removeAllGemNodeDescendantsInGivenDrawerOnly((GemDrawer)selectedNode);
            removeAllChildrenExceptForVisibleDescendantDrawers(selectedNode);           
           
        } else {
            // Now, remove all the children of the selected node, and re-insert them into categories.
            selectedNode.removeAllGemNodeDescendants();
            selectedNode.removeAllChildren();
        }

        int nCategories = categories.size();
        for (int i = 0; i < nCategories; i++) {

            GemCategory category = categories.get(i);
           
            // Create the category node.
            BrowserTreeNode categoryNode = selectedNode.addNewCategoryNode(category, nodeReuseInfo);
                       
            // Add the entity nodes to the category node.
            List<GemEntity> categoryItems = category.getCategoryItems();
            int numEntities = categoryItems.size();
            for (int j = 0; j < numEntities; j++) {
                GemEntity gemEntity = categoryItems.get(j);
                if (selectedNode instanceof SearchResultRootTreeNode) {
                    categoryNode.add(new SearchResultGemTreeNode(gemEntity, workingModuleTypeInfo));
                } else {
                    GemTreeNode node = new GemTreeNode(gemEntity, workingModuleTypeInfo);
                    categoryNode.add(node);
                   
                    // This node is guaranteed to be in the tree, so set it to be the node associated with the gemEntity
                    entityToTreeNodeMap.put(gemEntity, node);
                }
            }
        }
   
        // Notify the model about the changes.
        invokeOnEventDispatchThread(new Runnable() {
            public void run() {
                nodeStructureChanged(selectedNode);
            }
        });
    }

    /**
     * Starting from the currently selected node, we categorize the currently
     * selected node's children according to the child's arity.
     * Note: Categorize means that the children will be put into different folders based on its arity.
     * @param selectedNode The node below which all entities will be categorized.
     */
    public void categorizeByArity(BrowserTreeNode selectedNode) {  

        // Categorize by arity
        GemCategorizer<Integer> categorizer = new GemCategorizer<Integer>() {
          
            @Override
            public List<GemCategory.CategoryKey<Integer>> getCategoryKeyList(GemEntity gemEntity) {
                return Collections.singletonList(new GemCategory.CategoryKey<Integer>(Integer.valueOf(gemEntity.getTypeArity())));
            }

            @Override
            public int compareKey(CategoryKey<Integer> key1, CategoryKey<Integer> key2) {
                return key1.getValue().compareTo(key2.getValue());
            }
           
        };

        categorizeByX(selectedNode, categorizer);
    }

    /**
     * Starting from the currently selected node, we categorize the currently
     * selected node's children according to the child's gem type (type signature).
     * Note: Categorize means that the children will be put into different folders based on their type.
     * @param selectedNode The node below which all entities will be categorized.
     */
    public void categorizeByGemType(BrowserTreeNode selectedNode) { 

        // Categorize by type expr
        GemCategorizer<String> categorizer = new GemCategorizer<String>() {

            private final ScopedEntityNamingPolicy namingPolicy = new UnqualifiedUnlessAmbiguous(perspective.getWorkingModuleTypeInfo());
           
            @Override
            public List<GemCategory.CategoryKey<String>> getCategoryKeyList(GemEntity gemEntity) {
                return Collections.singletonList(new GemCategory.CategoryKey<String>(gemEntity.getTypeExpr().toString(namingPolicy)));
            }

            // Sort categories alphabetically
            @Override
            public int compareKey(GemCategory.CategoryKey<String> key1, GemCategory.CategoryKey<String> key2) {
                return key1.getValue().compareTo(key2.getValue());
            }
        };

        categorizeByX(selectedNode, categorizer);
    }

    /**
     * Starting from the currently selected node, we categorize the currently
     * selected node's children according to the child's output type.
     * Note: Categorize means that the children will be put into different folders based on its arity.
     * @param selectedNode The node below which all entities will be categorized.
     */
    public void categorizeByOutput(BrowserTreeNode selectedNode) { 

        // Categorize by output
        GemCategorizer<String> categorizer = new GemCategorizer<String>() {
           
            private final ScopedEntityNamingPolicy namingPolicy = new UnqualifiedUnlessAmbiguous(perspective.getWorkingModuleTypeInfo());
           
            @Override
            public List<GemCategory.CategoryKey<String>> getCategoryKeyList(GemEntity gemEntity) {
                return Collections.singletonList(new GemCategory.CategoryKey<String>(gemEntity.getTypeExpr().getResultType().toString(namingPolicy)));
            }

            // Sort categories alphabetically
            @Override
            public int compareKey(GemCategory.CategoryKey<String> key1, GemCategory.CategoryKey<String> key2) {
                return key1.getValue().compareTo(key2.getValue());
            }
        };

        categorizeByX(selectedNode, categorizer);
    }
   
    /**
     * Starting from the currently selected node, we categorize the currently
     * selected node's children according to the child's input type.
     * Note: Categorize means that the children will be put into different folders based on their input type.
     * @param selectedNode The node below which all entities will be categorized.
     */
    public void categorizeByInput(BrowserTreeNode selectedNode) { 

        // Categorize by input
        GemCategorizer<String> categorizer = new GemCategorizer<String>() {
           
            // the category key to use for gems with no inputs
            private final GemCategory.CategoryKey<String> noInputsKey = new GemCategory.CategoryKey<String>(BrowserMessages.getString("GB_NoInputs"));

            private final ScopedEntityNamingPolicy namingPolicy = new UnqualifiedUnlessAmbiguous(perspective.getWorkingModuleTypeInfo());
           
            // Each gem belongs to the category of each of its argument types
            @Override
            public List<GemCategory.CategoryKey<String>> getCategoryKeyList(GemEntity gemEntity) {
                int arity = gemEntity.getTypeExpr().getArity();
               
                if(arity == 0) {
                    return Collections.singletonList(noInputsKey);
                }
               
                Set<GemCategory.CategoryKey<String>> categories = new HashSet<GemCategory.CategoryKey<String>>();
                for (int i = 0; i < arity; i++) {
                    String categoryName = gemEntity.getTypeExpr().getTypePieces()[i].toString(namingPolicy);
                    categories.add(new GemCategory.CategoryKey<String>(categoryName));
                }

                return new ArrayList<GemCategory.CategoryKey<String>>(categories);
            }
           
            // Sort categories alphabetically
            @Override
            public int compareKey(GemCategory.CategoryKey<String> key1, GemCategory.CategoryKey<String> key2) {
               
                // always sort no inputs to the top before everything else
                if (key1.equals(key2)) {
                    return 0;
                }
               
                if (key1.equals(noInputsKey)) {
                    return -1;
                }
               
                if (key2.equals(noInputsKey)) {
                    return 1;
                }
               
                return key1.getValue().compareTo(key2.getValue());
            }
        };
       
        categorizeByX(selectedNode, categorizer);
    }   
   
    /**
     * Starting from the currently selected node, we categorize the currently
     * selected node's children according to the child's module.
     * @param selectedNode The node below which all entities will be categorized.
     * @param moduleTreeDisplayMode the display mode for the module tree.
     */
    public void categorizeByModule(BrowserTreeNode selectedNode, TreeViewDisplayMode moduleTreeDisplayMode) {

        // Create the categorizer.
        GemCategorizer<GemDrawerCategoryInfo> categorizer = getModuleCategorizer(selectedNode, moduleTreeDisplayMode);
       
        // Provide a custom node for the module.
        CategoryNodeProvider provider = new MaterialGemDrawerCategoryNodeProvider(moduleTreeDisplayMode);

        categorizeByX(selectedNode, categorizer, provider);
    }

    /**
     * Get a ModuleCategorizer based on the current perspective.
     * @param browserTreeNode the tree node for which the categorizer will be provided.
     * @param moduleTreeDisplayMode the display mode for the module tree.
     * @return a ModuleCategorizer for that node.
     */
    private ModuleCategorizer getModuleCategorizer(BrowserTreeNode browserTreeNode, TreeViewDisplayMode moduleTreeDisplayMode) {

        // If it's the first level under the workspace root, include all modules. 
        //   Otherwise, only include those modules which actually contain any gems.
        List<GemDrawerCategoryInfo> categoriesToInclude = (browserTreeNode == workspaceTreeNode) ? getGemDrawerCategoryInfoList(moduleTreeDisplayMode) : Collections.<GemDrawerCategoryInfo>emptyList();
        return new ModuleCategorizer(categoriesToInclude, true, moduleTreeDisplayMode);
    }

    /**
     * @param moduleTreeDisplayMode the display mode for the module tree.
     * @return the list of pairs (drawer name, isNamespaceNode), ordered according to the current visibility policy of the vault.
     * ie. Visible modules in alphabetical order followed optionally by non-visible modules in alphabetical order.
     */
    private List<GemDrawerCategoryInfo> getGemDrawerCategoryInfoList(final TreeViewDisplayMode moduleTreeDisplayMode) {
        List<GemDrawerCategoryInfo> drawerNamesList = new ArrayList<GemDrawerCategoryInfo>();
       
        List<MetaModule> visibleModules = new ArrayList<MetaModule>();
        CollectionUtils.select(perspective.getVisibleMetaModules(), new MetaModuleListFilter(), visibleModules);
        List<MetaModule> hiddenModules = perspective.getInvisibleMetaModules();
       
        // Collect all module names for creating a module name resolver
        Set<ModuleName> moduleNames = new HashSet<ModuleName>();
        for (final MetaModule visibleModule : visibleModules) {
            moduleNames.add(visibleModule.getName());
        }
        for (final MetaModule hiddenModule: hiddenModules) {
            moduleNames.add(hiddenModule.getName());
        }
       
        ModuleNameResolver workspaceModuleNameResolver = ModuleNameResolver.make(moduleNames);
       
        // Add the visible modules in alphabetical order.
        Collections.sort(visibleModules, getModuleAlphaComparator(workspaceModuleNameResolver, moduleTreeDisplayMode));

        for (final MetaModule metaModule : visibleModules) {
            drawerNamesList.add(new GemDrawerCategoryInfo(metaModule.getName(), false));
        }

        // If we are supposed to show all modules then add the invisible ones.
        if (showAllModules) {
            Collections.sort(hiddenModules, getModuleAlphaComparator(workspaceModuleNameResolver, moduleTreeDisplayMode));
           
            for (final MetaModule module : hiddenModules) {
                drawerNamesList.add(new GemDrawerCategoryInfo(module.getName(), false));
            }
        }
       
        return drawerNamesList;
    }
   
    /**
     * Starting from the currently selected node, we sort the currently selected node's
     * children according to the given sorter.
     * @param selectedNode The node below which all entities will be sorted.
     * @param sorter the sorter to use.
     */
    private void sortByX(final BrowserTreeNode selectedNode, GemComparatorSorter sorter) {

        List<GemEntity> childList = getAllEntities(selectedNode);
        List<GemEntity> sortedList = sorter.getSortedList(childList);
       
        // Attach the sorter to the selected node.
        selectedNode.setSorter(sorter);
       
        // Now, remove all the children of the selected node, and re-insert them in correct order.
        selectedNode.removeAllChildren();  

        // Add the entity nodes in the correct sorting order
        int numEntities = sortedList.size();
        for (int j = 0; j < numEntities; j++) {
           
            GemEntity gemEntity = sortedList.get(j);
           
            if (selectedNode instanceof SearchResultRootTreeNode) {
                selectedNode.add(new SearchResultGemTreeNode(gemEntity, perspective.getWorkingModuleTypeInfo()));
            } else {
                GemTreeNode node = new GemTreeNode(gemEntity, perspective.getWorkingModuleTypeInfo());
                selectedNode.add(node);
               
                // This node is guaranteed to be in the tree, so set it to be the node associated with the gemEntity
                entityToTreeNodeMap.put(gemEntity, node);
            }               
        }

        // Notify the model about the changes.
        invokeOnEventDispatchThread(new Runnable() {
            public void run() {
                nodeStructureChanged(selectedNode);
            }
        });
    }

    /**
     * Starting from the currently selected node, we sort the currently selected node's
     * children according to the alphabet:  A, a, B, b, etc...
     * Side Note: This method assumes that the gems will have a name of length greater than 0.
     * @param selectedNode The node below which all entities will be sorted.
     */
    public void sortByUnqualifiedName(BrowserTreeNode selectedNode) {
        sortByX(selectedNode, new GemUnqualifiedNameCaseInsensitiveSorter());
    }

    /**
     * Starting from the currently selected node, we sort the currently selected node's
     * children according to its form (Data Constructor, Supercombinator, Class Method, ...)
     * @param treeNode the node below which all entities will be sorted.
     */
    public void sortByForm(BrowserTreeNode treeNode) {
        sortByX(treeNode, new GemFormSorter());
    }

    /**
     * Determines whether modules not visible from the current perspective should be shown
     * in the tree view. If you change this property you have to reload the module.
     *
     * @param showAllModules if true all modules will be shown, if false only visible modules
     */
    public void setShowAllModules(boolean showAllModules) {
        this.showAllModules = showAllModules;
    }

    /**
     * Determines whether modules not visible from the current perspective should be shown
     * in the tree view. This value may not reflect what is currently shown in the view
     * since the model needs to be reloaded if the property is changed.
     *
     * @return boolean true if all modules should be shown, false if only visible modules should be shown
     */
    public boolean getShowAllModules() {
        return showAllModules;
    }

    /**
     * Determines whether only public gems should be displayed. The value may not reflect what
     * is currently shown in the view, since the model needs to be reloaded if the property is changed.
     *
     * @param showPublicGemsOnly true if only public gems should be shown, false if all gems
     * (public, private, protected)should be shown.
     */
    public void setShowPublicGemsOnly(boolean showPublicGemsOnly) {
        this.showPublicGemsOnly = showPublicGemsOnly;
    }
   
    /**
     * Determines whether only public gems should be displayed. The value may not reflect what
     * is currently shown in the view, since the model needs to be reloaded if the property is changed.
     *
     * @return boolean true is only public gems should be shown, false if all gems
     * (public, private, protected)should be shown.
     */
    public boolean getShowPublicGemsOnly() {
        return showPublicGemsOnly;
    }

    /**
     * Reverts the browser tree model to the "original" structure.
     * Drawers are organized in import-dependent order - the order depends on the order of imports into the current drawer
     *   (based on the perspective at the time the model was populated).
     * Entities are organized in alphabetical order within drawers.
     * @param moduleTreeDisplayMode the display mode for the module tree.
     */
    public void arrangeAsDefaultTreeStructure(TreeViewDisplayMode moduleTreeDisplayMode) {

        // Gems appear in alphabetical order on their unqualified name.
        sortByUnqualifiedName(workspaceTreeNode);

        // Categorize the first level by module.
        categorizeByModule(workspaceTreeNode, moduleTreeDisplayMode);
    }

    /**
     * Returns the tree node that represents the "Search Results" node.
     * @return the search results tree node
     */
    public BrowserTreeNode getSearchResultsNode () {
        return searchResultsTreeNode;
    }

    /**
     * Returns the tree node that represents the "Workspace" node.
     * @return the workspace node
     */
    public BrowserTreeNode getWorkspaceNode () {
        return workspaceTreeNode;
    }

    /**
     * Sets the name of the Workspace node in the tree to include the name of
     * the current workspace in square brackets.
     * @param name the name to have in square brackets
     */
    public void setWorkspaceNodeName(String name) {
        String fullName = BrowserMessages.getString("GB_WorkspaceNodeName") + " [" + name + "]";
        getWorkspaceNode().setUserObject(fullName);
        workspaceAbstractTreeNode.setName(fullName);
    }

    /**
     * Performs a sub-string search on all node names and type expressions in the model
     * and inserts nodes for matching gems into the search results node. The search is
     * case insensitive.
     * @param searchString the string to search for
     */
    public void doSearch (String searchString) {

        searchResultsTreeNode.removeAllChildren();
       
        // If the search string is empty we don't do anything
        if (searchString.trim().equals("")) {
            nodeStructureChanged(searchResultsTreeNode);
            lastSearchString = null;
            return;
        }
       
        // We want to search case insensitively.
        Pattern searchPattern = Pattern.compile(WildcardPatternMatcher.wildcardPatternToRegExp(searchString), Pattern.CASE_INSENSITIVE);
       
        // Keep a list of all the matching gem nodes.
        List<GemEntity> resultList = new ArrayList<GemEntity>();
        for (final GemEntity gemEntity : entityToTreeNodeMap.keySet()) {
            if (!showAllModules && !isVisibleGem(gemEntity)) {
                continue;
            }
           
            String nameString = gemEntity.getName().getUnqualifiedName();
            String typeExprQualifiedString = gemEntity.getTypeExpr().toString();
            String typeExprUnqualifiedString = gemEntity.getTypeExpr().toString(ScopedEntityNamingPolicy.UNQUALIFIED);
           
            // we use find() instead of matches() on the Matcher since we want to simply find a matching subsequence
            // and not whether the *whole* string matches the pattern.
            if (searchPattern.matcher(nameString).find() ||
                searchPattern.matcher(typeExprUnqualifiedString).find() ||
                searchPattern.matcher(typeExprQualifiedString).find()) {
                    resultList.add(gemEntity);
                }
        }         
       
        GemComparatorSorter sorter = new GemUnqualifiedNameCaseInsensitiveSorter();
        List<GemEntity> sortedList = sorter.getSortedList(resultList);
       
        // Add the matching gem nodes to the results node.
        for (final GemEntity gemEntity : sortedList) {
            searchResultsTreeNode.add(new SearchResultGemTreeNode(gemEntity, perspective.getWorkingModuleTypeInfo()));
        }
              
        nodeStructureChanged(searchResultsTreeNode);

        lastSearchString = searchString;
    }           

    public String getSearchString() {
        return lastSearchString;
    }
   
    /**
     * Reloads the model from the perspective it was originally populated from and uses the
     * same parameters as when originally populated. Tree structure state is not remembered
     * by the model. Use GemBrowser.refresh() to reload the model and maintain the tree structure.
     *
     * @see BrowserTreeModel#populate
     * @see org.openquark.gems.client.browser.GemBrowser#refresh()
     */
    @Override
    public void reload() {
       
        // Clear the existing drawers and nodes
        clearDrawers();
        entityToTreeNodeMap.clear();
       
        // Repopulate the model with the previous perspective.
        populate (perspective, showAllModules, TreeViewDisplayMode.FLAT_ABBREVIATED); // this choice of TreeViewDisplayMode shouldn't matter at all, so we just choose the default...
       
        // Update node categorizers on the model.
        updateModuleCategorizers();
       
        // Repeat the previous search to restore the search node.
        // Some new gems might have been added that need to be included in the search.
        if (lastSearchString != null) {
            doSearch (lastSearchString);
        }

        // Apply any categorization or sorting that is associated with the search node.
        GemComparatorSorter sorter = searchResultsTreeNode.getSorter();
        if (sorter != null) {
            sortByX(searchResultsTreeNode, sorter);
        }
       
        GemCategorizer<?> categorizer = searchResultsTreeNode.getCategorizer();
        if (categorizer != null) {
            categorizeByX(searchResultsTreeNode, categorizer, searchResultsTreeNode.getNodeProvider());
        }

        super.reload();
    }
   
    /**
     * Update any module categorizers held by nodes in the tree model to hold on to the current list of modules.
     *   This method should be invoked if the modules to display in the tree are changed.
     */
    private void updateModuleCategorizers() {
        for (Enumeration<TreeNode> nodeEnum = UnsafeCast.unsafeCast(((BrowserTreeNode)getRoot()).breadthFirstEnumeration()); nodeEnum.hasMoreElements(); ) {
            BrowserTreeNode nextNode = (BrowserTreeNode)nodeEnum.nextElement();
            GemCategorizer<?> categorizer = nextNode.getCategorizer();

            if (categorizer != null) {
                if (categorizer instanceof ModuleCategorizer) {
                    categorizer = getModuleCategorizer(nextNode, ((ModuleCategorizer)categorizer).getModuleTreeDisplayMode());
                }
                // recategorize, re-using previously existing categories if possible..
                categorizeByX(nextNode, categorizer, nextNode.getNodeProvider(), true);
            }
        }
    }
   
    /**
     * Populate the model with entities.
     * Note: any modules which already exist both in the tree and in the program will be replaced by the program definition.
     * @param perspective the perspective from which entities are gathered.
     * @param showAllModules whether modules not visible from the given perspective should also be visible.
     * @param moduleTreeDisplayMode the display mode for the module tree.
     */
    public void populate(final Perspective perspective, final boolean showAllModules, final TreeViewDisplayMode moduleTreeDisplayMode) {
       
        this.perspective = perspective;
        this.showAllModules = showAllModules;

        // A set of all GemEntities in the modules.
        Set<GemEntity> allEntities = new LinkedHashSet<GemEntity>();
       
        // Get all visible modules for this perspective.
        List<MetaModule> visibleModules = new ArrayList<MetaModule>();
        CollectionUtils.select(perspective.getVisibleMetaModules(), new MetaModuleListFilter(), visibleModules);
       
        List<MetaModule> hiddenModules = perspective.getInvisibleMetaModules();
       
        // Collect all module names for creating a module name resolver
        Set<ModuleName> moduleNames = new HashSet<ModuleName>();
        for (final MetaModule metaModule : visibleModules) {
            moduleNames.add(metaModule.getName());
        }
        for (final MetaModule metaModule : hiddenModules) {
            moduleNames.add(metaModule.getName());
        }
       
        // Refresh the module name resolver with the current set of module names
        workspaceModuleNameResolver = ModuleNameResolver.make(moduleNames);

        Collections.sort(visibleModules, getModuleAlphaComparator(workspaceModuleNameResolver, moduleTreeDisplayMode));
       
        // Add drawers for each of the modules.
        for (int i = 0, size = visibleModules.size(); i < size; i++) {
            MetaModule module = visibleModules.get(i);
            ModuleName moduleName = module.getName();
            Set<GemEntity> visibleEntitySet = perspective.getVisibleGemEntities(module);
            addDrawer(moduleName, visibleEntitySet);
            allEntities.addAll(visibleEntitySet);
        }
       
        if (showAllModules) {
            // If we are supposed to show all modules then add the invisible ones.
            Collections.sort(hiddenModules, getModuleAlphaComparator(workspaceModuleNameResolver, moduleTreeDisplayMode));
           
            // Add drawers for each of the modules.
            for (final MetaModule module : hiddenModules) {
                ModuleName moduleName = module.getName();

                // Create a set with all the public entities from the module.
                Set<GemEntity> publicEntitySet = perspective.getVisibleGemEntities(module);
   
                addDrawer(moduleName, publicEntitySet);
               
                allEntities.addAll(publicEntitySet);
            }
        }

        // Make sure tree nodes for all the entities exist.
        for (final GemEntity gemEntity : allEntities) {
            if (entityToTreeNodeMap.containsKey(gemEntity)) {
                continue;       // the entity is not new.

            }

            GemTreeNode newNode = new GemTreeNode(gemEntity, perspective.getWorkingModuleTypeInfo());
            entityToTreeNodeMap.put(gemEntity, newNode);
        }
       
        // Sort the set into a list by unqualified name. This way all the gems
        // appear in alphabetical order in whatever category they fall into.
        GemComparatorSorter sorter = new GemUnqualifiedNameCaseInsensitiveSorter();
        List<GemEntity> sortedList = sorter.getSortedList(allEntities);
       
        // Fill up the workspace node with the entities
        fillTreeStructure(workspaceTreeNode, sortedList);

        invokeOnEventDispatchThread(new Runnable() {
            public void run() {
                nodeStructureChanged(workspaceTreeNode);
            }
        });
    }
   
    /**
     * Needed for Swing consistency. Check which thread the event is currently on.
     * If already on the AWT event thread, just run; else place the event on the AWT event thread queue.

     * @param runnable the event that needs to run on the AWT event thread
     */
    static void invokeOnEventDispatchThread(final Runnable runnable) {
        if (EventQueue.isDispatchThread()) {
            runnable.run();
        } else {
            SwingUtilities.invokeLater(runnable);
        }
    }
   
    /**
     * Helper method to populate the model with an abstract tree node for a gem drawer.
     * @param moduleName the name of the drawer.
     * @param entitySet The gem entities which "belong" under this drawer.
     *   Nodes will be created for these gem entities and will be put into the drawer named drawerName.
     */
    private void addDrawer(ModuleName moduleName, Set<GemEntity> entitySet) {
       
        // Get the corresponding drawer.  Create and add it if it doesn't already exist.
        GemDrawerAbstractTreeNode drawerNode = (GemDrawerAbstractTreeNode)workspaceAbstractTreeNode.getChild(getGemDrawerNameForModule(moduleName));
        if (drawerNode == null) {
            drawerNode = new MaterialGemDrawerAbstractTreeNode(moduleName);
            workspaceAbstractTreeNode.addChild(drawerNode);
        }

        // Make sure the drawer is empty
        drawerNode.clearChildren();
           
        // Now add back new EntityNodes for the entities.
        for (final GemEntity gemEntity : entitySet) {
            EntityAbstractTreeNode entityNode = new EntityAbstractTreeNode(gemEntity);
            drawerNode.addChild(entityNode);
        }
    }
   
    /**
     * Returns the actual name of a gem drawer for a module.
     * @param moduleName the name of the module.
     * @return the corresponding gem drawer name.
     */
    static String getGemDrawerNameForModule(ModuleName moduleName) {
        return moduleName.toSourceText();
    }  
   
    /**
     * @param workspaceModuleNameResolver the module name resolver to use to generate an appropriate module name
     * @param moduleTreeDisplayMode
     * @return the moduleAlphaComparator
     */
    private static Comparator<MetaModule> getModuleAlphaComparator(final ModuleNameResolver workspaceModuleNameResolver, final TreeViewDisplayMode moduleTreeDisplayMode) {
       
        return new Comparator<MetaModule>() {
            public int compare(final MetaModule m1, final MetaModule m2) {
                final String gemDrawerNameForModule1 = ModuleNameDisplayUtilities.getDisplayNameForModuleInTreeView(m1.getName(), workspaceModuleNameResolver, moduleTreeDisplayMode);
                final String gemDrawerNameForModule2 = ModuleNameDisplayUtilities.getDisplayNameForModuleInTreeView(m2.getName(), workspaceModuleNameResolver, moduleTreeDisplayMode);
               
                return gemDrawerNameForModule1.compareTo(gemDrawerNameForModule2);
            }
        };
    }

    /**
     * Fill an empty TreeNode structure with gems, using any previously-used policies.
     * Preconditions: subtree is empty, entities map to GemTreeNodes
     * @param rootNode the root of the subtree to fill
     * @param entityList the gems that should go into the subtree
     */
    private void fillTreeStructure(BrowserTreeNode rootNode, List<GemEntity> entityList) {

        // Remove all old children of this node
        if (rootNode instanceof GemDrawer) {
            removeAllChildrenExceptForVisibleDescendantDrawers(rootNode);           
        } else {
            rootNode.removeAllChildren();
        }

        // Apply any sorter associated with this node
        GemSorter sorter = rootNode.getSorter();
        if (sorter != null) {
            entityList = sorter.getSortedList(entityList);
        }
       
        // Apply any categorization associated with this node
        GemCategorizer<?> categorizer = rootNode.getCategorizer();
        if (categorizer != null) {

            List<GemCategory> categoryList = categorizer.formCategories(entityList);

            // Iterate over the categories
            int nCategories = categoryList.size();
            for (int i = 0; i < nCategories; i++) {
   
                GemCategory category = categoryList.get(i);
               
                // Special case handling for gem drawer categories.
                // Don't show hidden modules if we're not supposed to.
                if (rootNode.getNodeProvider() instanceof MaterialGemDrawerCategoryNodeProvider && !showAllModules) {
                   
                    GemDrawerCategoryInfo pair = (GemDrawerCategoryInfo)(category.getCategoryKey()).getValue();
                   
                    if (!isVisibleModule(pair.getModuleName())) {
                        continue;
                    }
                }
               
                BrowserTreeNode categoryNode = rootNode.addNewCategoryNode(category);

                // Recurse into that node.
                fillTreeStructure(categoryNode, category.getCategoryItems());
            }

        } else {          
            // No categorizer.
            // This means this level isn't categorized - all entities go in here.

            for (final GemEntity gemEntity : entityList) {

                // display all gems, else only add the public gems
                if (!showPublicGemsOnly || gemEntity.getScope().isPublic()){
                    rootNode.add(getTreeNode(gemEntity));
                }
            }
        }
    }

    /**
     * Removes all children except for those whose descendants include visible drawers.
     * @param rootNode the root of the tree to process.
     */
    private void removeAllChildrenExceptForVisibleDescendantDrawers(BrowserTreeNode rootNode) {
        List<GemDrawer> subDrawersToKeep = new ArrayList<GemDrawer>();
        rootNode.collectAllGemDrawerDescendants(subDrawersToKeep);
       
        for (Iterator<GemDrawer> it = subDrawersToKeep.iterator(); it.hasNext(); ) {
            if (!isDrawerInAbstractTree(it.next().getModuleName().toSourceText())) {
                it.remove();
            }
        }
       
        for (int i = rootNode.getChildCount()-1; i >= 0; i--) {
            final TreeNode child = rootNode.getChildAt(i);
           
            if (child instanceof GemDrawer) {
               
                // we do not remove the child if it, or a descendant of it, is a drawer that is supposed to be visible
                boolean shouldRemove = true;
                for (int j = 0, n = subDrawersToKeep.size(); j < n; j++) {
                    if (((BrowserTreeNode)child).isNodeDescendant(subDrawersToKeep.get(j))) {
                        shouldRemove = false;
                    }
                }
               
                if (shouldRemove) {
                    rootNode.remove(i);
                }
            } else {
                rootNode.remove(i);
            }
        }
    }
   
    /**
     * Clear all gem drawers from the workspace.
     */
    public void clearDrawers() {
        workspaceAbstractTreeNode.clearChildren();
    }

    /**
     * @return the perspective the model was populated with
     */   
    Perspective getPerspective() {
        return perspective;
    }

    /**
     * Refreshes with a new perspective.
     * @param perspective the new perspective
     */
    public void setPerspectiveAndReload(Perspective perspective) {
        this.perspective = perspective;
        reload();
    }

    /**
     * Returns whether the given drawer is in the abstract tree.
     * @param drawerName the name of the drawer.
     * @return true if the drawer is in the abstract tree (i.e. it will be displayed), false otherwise.
     */
    private boolean isDrawerInAbstractTree(final String drawerName) {
        return (workspaceAbstractTreeNode.getChild(drawerName) != null);
    }
}
TOP

Related Classes of org.openquark.gems.client.browser.BrowserTreeModel$MetaModuleListFilter

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.
y> ga('send', 'pageview');