/* * 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. */ /* * NavTreeModel.java * Creation date: Jul 3, 2003 * By: Frank Worsley */ package org.openquark.gems.client.navigator; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.SortedMap; import java.util.TreeMap; import javax.swing.SwingUtilities; import javax.swing.tree.DefaultTreeModel; import org.openquark.cal.compiler.ClassInstance; import org.openquark.cal.compiler.Function; import org.openquark.cal.compiler.FunctionalAgent; import org.openquark.cal.compiler.ModuleName; import org.openquark.cal.compiler.ModuleNameResolver; import org.openquark.cal.compiler.ModuleTypeInfo; import org.openquark.cal.compiler.TypeClass; import org.openquark.cal.compiler.TypeConstructor; import org.openquark.cal.services.MetaModule; import org.openquark.cal.services.Perspective; import org.openquark.gems.client.ModuleNameDisplayUtilities.TreeViewDisplayMode; /** * This class implements a TreeModel for the NavTree. * It takes care of loading modules and CAL entities from the perspective and categorizing them in the tree. * * @author Frank Worsley */ public class NavTreeModel extends DefaultTreeModel { private static final long serialVersionUID = -1302161883211389665L; /** The root node of the model. */ private final NavTreeNode rootNode; /** The map that maps the location part of cal:// urls to their respective nodes. */ private final Map<NavAddress, NavTreeNode> urlToNodeMap = new HashMap<NavAddress, NavTreeNode>(); /** * Constructor for a new NavTreeModel. */ public NavTreeModel() { super(new NavRootNode()); this.rootNode = getRoot(); urlToNodeMap.put(rootNode.getAddress(), rootNode); } /** * {@inheritDoc} */ @Override public NavRootNode getRoot() { return (NavRootNode)super.getRoot(); } /** * This method loads CAL entities from the perspective and categorizes * them in the model. * @param treeViewDisplayMode the display mode for the tree. */ public void load(Perspective perspective, TreeViewDisplayMode treeViewDisplayMode) { rootNode.removeAllChildren(); List<MetaModule> modules = new ArrayList<MetaModule>(perspective.getVisibleMetaModules()); modules.addAll(perspective.getInvisibleMetaModules()); Set<ModuleName> moduleNames = new HashSet<ModuleName>(); for (final MetaModule metaModule : modules) { moduleNames.add(metaModule.getName()); } ModuleNameResolver workspaceModuleNameResolver = ModuleNameResolver.make(moduleNames); List<NavModuleNode> moduleNodes = new ArrayList<NavModuleNode>(modules.size()); for (final MetaModule metaModule : modules) { NavModuleNode moduleNode = makeModuleNode(metaModule, workspaceModuleNameResolver, treeViewDisplayMode); moduleNodes.add(moduleNode); } buildModuleTree(moduleNodes, treeViewDisplayMode); } /** * Builds the tree of module nodes. * @param moduleNodes a list of NavModuleNodes. * @param treeViewDisplayMode the display mode for the tree. */ private void buildModuleTree(final List<NavModuleNode> moduleNodes, final TreeViewDisplayMode treeViewDisplayMode) { if (treeViewDisplayMode != TreeViewDisplayMode.HIERARCHICAL) { addAllNodes(rootNode, moduleNodes, true); } else { // We are using a SortedMap so that a module namespace node "A.B" is added ahead of the module with the same name "A.B" final SortedMap<ModuleName, NavTreeNode> nodeMap = new TreeMap<ModuleName, NavTreeNode>(); // first we process all the existing module nodes final int nModules = moduleNodes.size(); for (int i = 0; i < nModules; i++) { NavModuleNode moduleNode = moduleNodes.get(i); nodeMap.put(moduleNode.getMetaModule().getName(), moduleNode); } // now we figure out what intermediate namespace nodes are required final Map<ModuleName, NavModuleNamespaceNode> namespaceNodeMap = new HashMap<ModuleName, NavModuleNamespaceNode>(); // a map from a module name (non-null) to a list of the children nodes final Map<ModuleName, List<NavTreeNode>> nodeChildrenMap = new HashMap<ModuleName, List<NavTreeNode>>(); // a list of the root's children final LinkedHashSet<NavTreeNode> rootChildrenList = new LinkedHashSet<NavTreeNode>(); for (final Map.Entry<ModuleName, NavTreeNode> entry : nodeMap.entrySet()) { final ModuleName moduleName = entry.getKey(); final NavTreeNode moduleNode = entry.getValue(); helpProcessParentChild(namespaceNodeMap, nodeChildrenMap, rootChildrenList, moduleName.getImmediatePrefix(), moduleNode); } // finally, build the tree for (final Map.Entry<ModuleName, List<NavTreeNode>> entry : nodeChildrenMap.entrySet()) { final ModuleName parentModuleName = entry.getKey(); final List<NavTreeNode> children = entry.getValue(); final NavTreeNode parentNode = namespaceNodeMap.get(parentModuleName); addAllNodes(parentNode, children, true); } // we deal with the root's children separately addAllNodes(rootNode, new ArrayList<NavTreeNode>(rootChildrenList), true); } SwingUtilities.invokeLater(new Runnable() { public void run() { nodeStructureChanged(rootNode); } }); } /** * Helper method for processing a pair of parent/child nodes in a hierarchically displayed tree. * @param namespaceNodeMap the map containing module namespace nodes (to be added to). * @param nodeChildrenMap the map from a module name (non-null) to a list of the children nodes (to be added to). * @param rootChildrenList a list of the root's children (to be added to). * @param parentModuleName the module name corresponding to the parent node. * @param child the child node. */ private void helpProcessParentChild( final Map<ModuleName, NavModuleNamespaceNode> namespaceNodeMap, final Map<ModuleName, List<NavTreeNode>> nodeChildrenMap, final LinkedHashSet<NavTreeNode> rootChildrenList, final ModuleName parentModuleName, final NavTreeNode child) { final Collection<NavTreeNode> childrenListForPrefix; if (parentModuleName == null) { childrenListForPrefix = rootChildrenList; } else { final List<NavTreeNode> childrenListForPrefixFromMap = nodeChildrenMap.get(parentModuleName); if (childrenListForPrefixFromMap == null) { final List<NavTreeNode> newChildrenListForPrefix = new ArrayList<NavTreeNode>(); childrenListForPrefix = newChildrenListForPrefix; nodeChildrenMap.put(parentModuleName, newChildrenListForPrefix); } else { childrenListForPrefix = childrenListForPrefixFromMap; } } childrenListForPrefix.add(child); if (parentModuleName != null) { if (namespaceNodeMap.containsKey(parentModuleName)) { helpProcessParentChild(namespaceNodeMap, nodeChildrenMap, rootChildrenList, parentModuleName.getImmediatePrefix(), namespaceNodeMap.get(parentModuleName)); } else { // if there is no module namespace node for the given prefix, then we // need a new module namespace node NavModuleNamespaceNode namespaceNode = new NavModuleNamespaceNode(parentModuleName); namespaceNodeMap.put(parentModuleName, namespaceNode); helpProcessParentChild(namespaceNodeMap, nodeChildrenMap, rootChildrenList, parentModuleName.getImmediatePrefix(), namespaceNode); } } } /** * This method loads all entities from a CAL module and adds tree nodes for them. * @param metaModule the module to load entities for. * @param workspaceModuleNameResolver the module name resolver to use to generate an appropriate module name * @param treeViewDisplayMode the display mode for the tree. * @return a node for the module. */ private NavModuleNode makeModuleNode(final MetaModule metaModule, final ModuleNameResolver workspaceModuleNameResolver, TreeViewDisplayMode treeViewDisplayMode) { ModuleTypeInfo moduleInfo = metaModule.getTypeInfo(); NavModuleNode moduleNode = new NavModuleNode(metaModule, workspaceModuleNameResolver, treeViewDisplayMode); urlToNodeMap.put(moduleNode.getAddress(), moduleNode); // Load all functions. FunctionalAgent[] entities = moduleInfo.getFunctionalAgents(); if (entities.length > 0) { NavVaultNode functionsNode = new NavFunctionVaultNode(); List<NavFunctionNode> functionNodes = new ArrayList<NavFunctionNode>(); for (final FunctionalAgent entity : entities) { if (entity instanceof Function) { functionNodes.add(new NavFunctionNode((Function) entity)); } } if (functionNodes.size() > 0) { moduleNode.add(functionsNode); urlToNodeMap.put(functionsNode.getAddress(), functionsNode); addAllNodes(functionsNode, functionNodes, false); } } // Load all the type constructors. int constructorCount = moduleInfo.getNTypeConstructors(); if (constructorCount > 0) { List<NavTreeNode> typeConstructorNodes = new ArrayList<NavTreeNode>(constructorCount); for (int i = 0; i < constructorCount; i++) { TypeConstructor typeConstructor = moduleInfo.getNthTypeConstructor(i); NavTreeNode typeConstructorNode = new NavTypeConstructorNode(typeConstructor); typeConstructorNodes.add(typeConstructorNode); // Load the data constructors. int dataConstructorCount = typeConstructor.getNDataConstructors(); for (int n = 0; n < dataConstructorCount; n++) { NavTreeNode dataConstructorNode = new NavDataConstructorNode(typeConstructor.getNthDataConstructor(n)); typeConstructorNode.add(dataConstructorNode); urlToNodeMap.put(dataConstructorNode.getAddress(), dataConstructorNode); } } NavVaultNode typeConstructorsNode = new NavTypeConstructorVaultNode(); moduleNode.add(typeConstructorsNode); urlToNodeMap.put(typeConstructorsNode.getAddress(), typeConstructorsNode); addAllNodes(typeConstructorsNode, typeConstructorNodes, false); } // Load all the type classes. int classCount = moduleInfo.getNTypeClasses(); if (classCount > 0) { List<NavTreeNode> classNodes = new ArrayList<NavTreeNode>(classCount); for (int i = 0; i < classCount; i++) { TypeClass typeClass = moduleInfo.getNthTypeClass(i); NavTreeNode typeClassNode = new NavTypeClassNode(typeClass); classNodes.add(typeClassNode); // Load the class methods. int methodCount = typeClass.getNClassMethods(); if (methodCount > 0) { List<NavClassMethodNode> classMethodNodes = new ArrayList<NavClassMethodNode>(methodCount); for (int n = 0; n < methodCount; n++) { classMethodNodes.add(new NavClassMethodNode(typeClass.getNthClassMethod(n))); } addAllNodes(typeClassNode, classMethodNodes, false); } } NavVaultNode classesNode = new NavTypeClassVaultNode(); moduleNode.add(classesNode); urlToNodeMap.put(classesNode.getAddress(), classesNode); addAllNodes(classesNode, classNodes, false); } // Load all the class instances int instanceCount = moduleInfo.getNClassInstances(); if (instanceCount > 0) { List<NavTreeNode> classInstanceNodes = new ArrayList<NavTreeNode>(instanceCount); for (int n = 0; n < instanceCount; n++) { ClassInstance instance = moduleInfo.getNthClassInstance(n); NavTreeNode classInstanceNode = new NavClassInstanceNode(moduleInfo.getNthClassInstance(n), moduleInfo); classInstanceNodes.add(classInstanceNode); // Load the instance methods. int methodCount = instance.getNInstanceMethods(); if (methodCount > 0) { List<NavInstanceMethodNode> instanceMethodNodes = new ArrayList<NavInstanceMethodNode>(methodCount); for (int k = 0; k < methodCount; k++) { String methodName = instance.getTypeClass().getNthClassMethod(k).getName().getUnqualifiedName(); instanceMethodNodes.add(new NavInstanceMethodNode(moduleInfo.getNthClassInstance(n), methodName, moduleInfo)); } addAllNodes(classInstanceNode, instanceMethodNodes, false); } } NavVaultNode classInstancesNode = new NavClassInstanceVaultNode(); moduleNode.add(classInstancesNode); urlToNodeMap.put(classInstancesNode.getAddress(), classInstancesNode); addAllNodes(classInstancesNode, classInstanceNodes, false); } return moduleNode; } /** * This method adds all nodes in the given list to the given parent node. * The nodes in the list are sorted before adding them. * @param parent the parent node to add nodes to * @param nodes the List of nodes to add * @param addToFront whether the new nodes are to be added ahead of the existing children. */ private void addAllNodes(final NavTreeNode parent, final List<? extends NavTreeNode> nodes, final boolean addToFront) { Collections.sort(nodes, NavTreeNode.NODE_SORTER); if (addToFront) { final int nNodes = nodes.size(); for (int i = nNodes - 1; i >= 0 ; i--) { NavTreeNode node = nodes.get(i); parent.insert(node, 0); urlToNodeMap.put(node.getAddress(), node); } } else { for (final NavTreeNode node : nodes) { parent.add(node); urlToNodeMap.put(node.getAddress(), node); } } } /** * @param url the url to find get the node for * @return the NavTreeNode that is associated with the specified url or null if there is no such node */ public NavTreeNode getNodeForAddress(NavAddress url) { return urlToNodeMap.get(url.withAnchor(null)); } }