/* * 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. */ /* * BrowserTreeNode.java * Creation date: Dec 19th 2002 * By: Ken Wong */ package org.openquark.gems.client.browser; import java.text.ChoiceFormat; import java.text.MessageFormat; import java.util.Enumeration; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.swing.tree.DefaultMutableTreeNode; import org.openquark.cal.compiler.ModuleName; import org.openquark.cal.services.GemComparatorSorter; import org.openquark.gems.client.browser.BrowserTreeModel.CategoryNodeProvider; import org.openquark.gems.client.browser.GemCategory.CategoryKey; import org.openquark.util.Pair; import org.openquark.util.UnsafeCast; /** * Specialized version of DefaultMutableTreeNode. * Adds a getDisplayedString method that is used by the BrowserTreeCellRenderer to get the * displayable string for a node. By default returns toString but can be overridden to * return more complex information. * This class also adds support for storing node sorting and categorization information. * @author Ken Wong * Creation Date: Dec 19th 2002 */ public abstract class BrowserTreeNode extends DefaultMutableTreeNode { /** The categorizer associated with this node. */ private GemCategorizer<?> categorizer; /** The category node provider for the categorizer that is associated with this node. */ private CategoryNodeProvider provider; /** The sorter that is associated with this node. */ private GemComparatorSorter sorter; /** A map of existing category children nodes. */ private final Map<CategoryKey<?>, BrowserTreeNode> categoryToNodeMap = new HashMap<CategoryKey<?>, BrowserTreeNode>(); /** * An object which can store mappings for a node's children and GemDrawer descendants, from which they can later be retrieved by displayed name/module name. * @author Edward Lam */ static class NodeReuseInfo { /** map from displayed name to the node with that name, for all of the node's children. */ private final Map<Pair<String, Class<? extends BrowserTreeNode>>, BrowserTreeNode> displayedStringToChildNodeReuseMap = new HashMap<Pair<String, Class<? extends BrowserTreeNode>>, BrowserTreeNode>(); /** map from module name to a descendent GemDrawer. */ private final Map<ModuleName, GemDrawer> moduleNameToDescendantGemDrawerReuseMap = new HashMap<ModuleName, GemDrawer>(); /** * Constructor for a NodeReuseInfo. * @param selectedNode the node for which this info will be constructed. */ private NodeReuseInfo(BrowserTreeNode selectedNode) { populateDisplayedStringToChildNodeReuseMap(selectedNode); populateModuleNameToDescendantGemDrawerReuseMap(selectedNode); } /** * Populate the displayedStringToChildNodeReuseMap * @param selectedNode */ private void populateDisplayedStringToChildNodeReuseMap(BrowserTreeNode selectedNode) { for (int i = 0, nChildren = selectedNode.getChildCount(); i < nChildren; i++) { BrowserTreeNode nextNode = (BrowserTreeNode)selectedNode.getChildAt(i); displayedStringToChildNodeReuseMap.put(new Pair<String, Class<? extends BrowserTreeNode>>(nextNode.getDisplayedString(), nextNode.getClass()), nextNode); } } /** * Populate the moduleNameToDescendantGemDrawerReuseMap * @param selectedNode */ private void populateModuleNameToDescendantGemDrawerReuseMap(BrowserTreeNode selectedNode) { for (int i = 0, nChildren = selectedNode.getChildCount(); i < nChildren; i++) { BrowserTreeNode nextNode = (BrowserTreeNode)selectedNode.getChildAt(i); if (nextNode instanceof GemDrawer) { moduleNameToDescendantGemDrawerReuseMap.put(((GemDrawer)nextNode).getModuleName(), (GemDrawer)nextNode); } populateModuleNameToDescendantGemDrawerReuseMap(nextNode); } } /** * Retrieves a cached node from the displayedStringToChildNodeReuseMap map. * @param displayedString the displayed string of the node. * @param nodeClass the class of the node. * @return the cached node, or null if one does not exist. */ BrowserTreeNode getChildNodeFromDisplayedString(String displayedString, Class<GemCategoryNode> nodeClass) { return displayedStringToChildNodeReuseMap.get(new Pair<String, Class<GemCategoryNode>>(displayedString, nodeClass)); } /** * Retrieves a cached node from the moduleNameToDescendantGemDrawerReuseMap map. * @param moduleName the module name corresponding to the node. * @return the cached node, or null if one does not exist. */ GemDrawer getDescendantGemDrawer(ModuleName moduleName) { return moduleNameToDescendantGemDrawerReuseMap.get(moduleName); } } /** * Generic constructor */ public BrowserTreeNode() { super(); } /** * Generic constructor * @param userObject */ public BrowserTreeNode(Object userObject) { super(userObject); } /** * Generic constructor * @param userObject * @param allowChildren */ public BrowserTreeNode(Object userObject, boolean allowChildren) { super(userObject, allowChildren); } /** * Returns the string that will be used by the cell renderer to label the tree node. * The default implementation simply returns toString, subclasses should override this * if they wish to return more complex information. * @return String */ public String getDisplayedString() { return toString(); } /** * Associates the given sorter with this tree node. * @param sorter the sorter to associated */ public void setSorter(GemComparatorSorter sorter) { this.sorter = sorter; // If we are sorted, we can't be categorized. categorizer = null; provider = null; categoryToNodeMap.clear(); } /** * Associates the given categorizer and node provider with this tree node. * @param categorizer the categorizer to associated * @param provider the node provider for the categorizer */ public void setCategorizer(GemCategorizer<?> categorizer, CategoryNodeProvider provider) { this.categorizer = categorizer; this.provider = provider; categoryToNodeMap.clear(); // If we are categorized, we can't be sorted. sorter = null; } /** * Returns the sorter associated with this node. * @return GemComparatorSorter */ public GemComparatorSorter getSorter() { return sorter; } /** * Returns the categorizer associated with this node. * @return GemCategorizer */ public GemCategorizer<?> getCategorizer() { return categorizer; } /** * Returns the node provider associated with this node. * @return CategoryNodeProvider */ public CategoryNodeProvider getNodeProvider() { return provider; } /** * Adds the tree node to use for representing a sub-category of this node. * This method will check if the tree node has been used before and add it * from the categoryToNodeMap if that is the case. Otherwise a new node will be * obtained from the node provider and stored in the map for future reuse. * We store the nodes in the map so that any categorization info associated with * the sub-category nodes is not lost if this node is re-categorized. * @param category the category for which to return a tree node * @return the tree node to use for the given category, which has been added to this node as a child. */ public BrowserTreeNode addNewCategoryNode(GemCategory category) { return addNewCategoryNode(category, null); } /** * Adds the tree node to use for representing a sub-category of this node. * This method will check if the tree node has been used before and add it * from the categoryToNodeMap if that is the case. Otherwise a new node will be * obtained from the node provider and stored in the map for future reuse. * We store the nodes in the map so that any categorization info associated with * the sub-category nodes is not lost if this node is re-categorized. * * @param category the category for which to return a tree node * @param nodeReuseInfo the node reuse info for this node, from which category nodes of the save name will be fetched and re-used. * If null, all category nodes will be newly-created. * @return the tree node to use for the given category, which has been added to this node as a child. */ BrowserTreeNode addNewCategoryNode(GemCategory category, NodeReuseInfo nodeReuseInfo) { BrowserTreeNode categoryNode = categoryToNodeMap.get(category.getCategoryKey()); if (categoryNode == null) { categoryNode = provider.addCategoryNodeToParent(this, category, nodeReuseInfo); } else { if (!this.isNodeChild(categoryNode)) { this.add(categoryNode); } } return categoryNode; } /** * Adds the given category node, or a cached version of it if one exists. * @param categoryNode the category node to be added. * @param category the category for the node. * @return the actual node that now exists as a child - either the given category node, or a cached version of it. */ BrowserTreeNode addCategoryNode(final BrowserTreeNode categoryNode, final GemCategory category) { final BrowserTreeNode categoryNodeFromMap = categoryToNodeMap.get(category.getCategoryKey()); if (categoryNodeFromMap != null) { if (!this.isNodeChild(categoryNodeFromMap)) { this.add(categoryNodeFromMap); } return categoryNodeFromMap; } else { categoryToNodeMap.put(category.getCategoryKey(), categoryNode); if (!this.isNodeChild(categoryNode)) { this.add(categoryNode); } return categoryNode; } } /** * @return ChildNameInfo for this node. */ NodeReuseInfo getChildNameInfo() { return new NodeReuseInfo(this); } /** * Removes each leaf node that descends from this node from its parent. */ void removeAllGemNodeDescendants() { for (int i = getChildCount() - 1; i >= 0; i--) { BrowserTreeNode currentNode = (BrowserTreeNode)getChildAt(i); if (currentNode instanceof GemTreeNode) { currentNode.removeFromParent(); } else { currentNode.removeAllGemNodeDescendants(); } } } /** * Removes each leaf node that descends from this node (but still within the scope of the same drawer) from its parent. */ void removeAllGemNodeDescendantsInGivenDrawerOnly(GemDrawer drawerNode) { if (this instanceof GemDrawer && this != drawerNode) { return; } for (int i = getChildCount() - 1; i >= 0; i--) { BrowserTreeNode currentNode = (BrowserTreeNode)getChildAt(i); if (currentNode instanceof GemTreeNode) { currentNode.removeFromParent(); } else { currentNode.removeAllGemNodeDescendantsInGivenDrawerOnly(drawerNode); } } } /** * Collects all GemDrawer descendants into the given list. * @param drawerList the list for collecting the GemDrawer descendants. */ void collectAllGemDrawerDescendants(final List<GemDrawer> drawerList) { for (int i = getChildCount() - 1; i >= 0; i--) { BrowserTreeNode currentNode = (BrowserTreeNode)getChildAt(i); if (currentNode instanceof GemDrawer) { drawerList.add((GemDrawer)currentNode); } currentNode.collectAllGemDrawerDescendants(drawerList); } } /** * Returns the string that will be used by the browser tree to display the tooltip * for this tree node. Subclasses that want to return a custom tooltip need to override this. * By default it returns the name of the node and information on the nodes in the hierachy * below this one. * @return String */ public String getToolTipText() { StringBuilder toolTipText = new StringBuilder("<html><b>" + getDisplayedString() + "</b><br>"); toolTipText.append(getSecondaryToolTipText()); toolTipText.append("</html>"); return toolTipText.toString(); } /** * Returns information about the node hierarchy (ie: number of gems and categories contained), * which is displayed in non-bold form in the tooltip. * @return String */ public String getSecondaryToolTipText() { int numberOfGems = 0; int numberOfCategories = 0; Enumeration<DefaultMutableTreeNode> children = UnsafeCast.unsafeCast(this.breadthFirstEnumeration()); // This node is returned first, so skip over it. children.nextElement(); while (children.hasMoreElements()) { BrowserTreeNode child = (BrowserTreeNode) children.nextElement(); if (child instanceof GemTreeNode && child.isLeaf()) { // collect information on the gems in this category numberOfGems++; } else if (child instanceof GemCategoryNode || child instanceof GemDrawer) { // keep track of the number of subcategories numberOfCategories++; } } // format our message from the resources Object[] arguments = { Integer.valueOf(numberOfGems), Integer.valueOf(numberOfCategories) }; double[] limits = {0, 1, 2}; String[] gemStrings = { BrowserMessages.getString("GB_NoGems"), BrowserMessages.getString("GB_OneGem"), BrowserMessages.getString("GB_ManyGems") }; String[] catStrings = { BrowserMessages.getString("GB_NoCategories"), BrowserMessages.getString("GB_OneCategory"), BrowserMessages.getString("GB_ManyCategories") }; ChoiceFormat gemChoice = new ChoiceFormat(limits, gemStrings); ChoiceFormat catChoice = new ChoiceFormat(limits, catStrings); ChoiceFormat[] formats = { gemChoice, catChoice }; MessageFormat message = new MessageFormat(BrowserMessages.getString("GB_NodeToolTip")); message.setFormats(formats); return message.format(arguments); } }