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