/*
 * Copyright (c) 2004, 2006, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package sun.tools.jconsole.inspector;

import java.awt.EventQueue;
import java.util.*;
import javax.management.*;
import javax.swing.*;
import javax.swing.tree.*;
import sun.tools.jconsole.JConsole;
import sun.tools.jconsole.MBeansTab;
import sun.tools.jconsole.Messages;
import sun.tools.jconsole.inspector.XNodeInfo;
import sun.tools.jconsole.inspector.XNodeInfo.Type;

@SuppressWarnings("serial")
public class XTree extends JTree {

    private static final List<String> orderedKeyPropertyList =
            new ArrayList<String>();
    static {
        String keyPropertyList =
                System.getProperty("com.sun.tools.jconsole.mbeans.keyPropertyList");
        if (keyPropertyList == null) {
            orderedKeyPropertyList.add("type");
            orderedKeyPropertyList.add("j2eeType");
        } else {
            StringTokenizer st = new StringTokenizer(keyPropertyList, ",");
            while (st.hasMoreTokens()) {
                orderedKeyPropertyList.add(st.nextToken());
            }
        }
    }

    private MBeansTab mbeansTab;

    private Map<String, DefaultMutableTreeNode> nodes =
            new HashMap<String, DefaultMutableTreeNode>();

    public XTree(MBeansTab mbeansTab) {
        this(new DefaultMutableTreeNode("MBeanTreeRootNode"), mbeansTab);
    }

    public XTree(TreeNode root, MBeansTab mbeansTab) {
        super(root);
        this.mbeansTab = mbeansTab;
        setRootVisible(false);
        setShowsRootHandles(true);
        ToolTipManager.sharedInstance().registerComponent(this);
    }

    
This method removes the node from its parent
/** * This method removes the node from its parent */
// Call on EDT private synchronized void removeChildNode(DefaultMutableTreeNode child) { DefaultTreeModel model = (DefaultTreeModel) getModel(); model.removeNodeFromParent(child); }
This method adds the child to the specified parent node at specific index.
/** * This method adds the child to the specified parent node * at specific index. */
// Call on EDT private synchronized void addChildNode( DefaultMutableTreeNode parent, DefaultMutableTreeNode child, int index) { // Tree does not show up when there is only the root node // DefaultTreeModel model = (DefaultTreeModel) getModel(); DefaultMutableTreeNode root = (DefaultMutableTreeNode) model.getRoot(); boolean rootLeaf = root.isLeaf(); model.insertNodeInto(child, parent, index); if (rootLeaf) { model.nodeStructureChanged(root); } }
This method adds the child to the specified parent node. The index where the child is to be added depends on the child node being Comparable or not. If the child node is not Comparable then it is added at the end, i.e. right after the current parent's children.
/** * This method adds the child to the specified parent node. * The index where the child is to be added depends on the * child node being Comparable or not. If the child node is * not Comparable then it is added at the end, i.e. right * after the current parent's children. */
// Call on EDT private synchronized void addChildNode( DefaultMutableTreeNode parent, DefaultMutableTreeNode child) { int childCount = parent.getChildCount(); if (childCount == 0) { addChildNode(parent, child, 0); } else if (child instanceof ComparableDefaultMutableTreeNode) { ComparableDefaultMutableTreeNode comparableChild = (ComparableDefaultMutableTreeNode)child; int i = 0; for (; i < childCount; i++) { DefaultMutableTreeNode brother = (DefaultMutableTreeNode) parent.getChildAt(i); //child < brother if (comparableChild.compareTo(brother) < 0) { addChildNode(parent, child, i); break; } //child = brother else if (comparableChild.compareTo(brother) == 0) { addChildNode(parent, child, i); break; } } //child < all brothers if (i == childCount) { addChildNode(parent, child, childCount); } } else { //not comparable, add at the end addChildNode(parent, child, childCount); } }
This method removes all the displayed nodes from the tree, but does not affect actual MBeanServer contents.
/** * This method removes all the displayed nodes from the tree, * but does not affect actual MBeanServer contents. */
// Call on EDT public synchronized void removeAll() { DefaultTreeModel model = (DefaultTreeModel) getModel(); DefaultMutableTreeNode root = (DefaultMutableTreeNode) model.getRoot(); root.removeAllChildren(); model.nodeStructureChanged(root); nodes.clear(); } public void delMBeanFromView(final ObjectName mbean) { EventQueue.invokeLater(new Runnable() { public void run() { // We assume here that MBeans are removed one by one (on MBean // unregistered notification). Deletes the tree node associated // with the given MBean and recursively all the node parents // which are leaves and non XMBean. // synchronized (XTree.this) { DefaultMutableTreeNode node = null; Dn dn = buildDn(mbean); if (dn.size() > 0) { DefaultTreeModel model = (DefaultTreeModel) getModel(); Token token = dn.getToken(0); String hashKey = dn.getHashKey(token); node = nodes.get(hashKey); if ((node != null) && (!node.isRoot())) { if (hasMBeanChildren(node)) { removeNonMBeanChildren(node); String label = token.getValue().toString(); XNodeInfo userObject = new XNodeInfo( Type.NONMBEAN, label, label, token.toString()); changeNodeValue(node, userObject); } else { DefaultMutableTreeNode parent = (DefaultMutableTreeNode) node.getParent(); model.removeNodeFromParent(node); nodes.remove(hashKey); delParentFromView(dn, 1, parent); } } } } } }); }
Returns true if any of the children nodes is an MBean.
/** * Returns true if any of the children nodes is an MBean. */
private boolean hasMBeanChildren(DefaultMutableTreeNode node) { for (Enumeration<?> e = node.children(); e.hasMoreElements();) { DefaultMutableTreeNode n = (DefaultMutableTreeNode) e.nextElement(); if (((XNodeInfo) n.getUserObject()).getType().equals(Type.MBEAN)) { return true; } } return false; }
Remove all the children nodes which are not MBean.
/** * Remove all the children nodes which are not MBean. */
private void removeNonMBeanChildren(DefaultMutableTreeNode node) { Set<DefaultMutableTreeNode> metadataNodes = new HashSet<DefaultMutableTreeNode>(); DefaultTreeModel model = (DefaultTreeModel) getModel(); for (Enumeration e = node.children(); e.hasMoreElements(); ) { DefaultMutableTreeNode n = (DefaultMutableTreeNode) e.nextElement(); if (!((XNodeInfo) n.getUserObject()).getType().equals(Type.MBEAN)) { metadataNodes.add(n); } } for (DefaultMutableTreeNode n : metadataNodes) { model.removeNodeFromParent(n); } }
Removes only the parent nodes which are non MBean and leaf. This method assumes the child nodes have been removed before.
/** * Removes only the parent nodes which are non MBean and leaf. * This method assumes the child nodes have been removed before. */
private DefaultMutableTreeNode delParentFromView( Dn dn, int index, DefaultMutableTreeNode node) { if ((!node.isRoot()) && node.isLeaf() && (!(((XNodeInfo) node.getUserObject()).getType().equals(Type.MBEAN)))) { DefaultMutableTreeNode parent = (DefaultMutableTreeNode) node.getParent(); removeChildNode(node); String hashKey = dn.getHashKey(dn.getToken(index)); nodes.remove(hashKey); delParentFromView(dn, index + 1, parent); } return node; } public synchronized void addMBeanToView(final ObjectName mbean) { final XMBean xmbean; try { xmbean = new XMBean(mbean, mbeansTab); if (xmbean == null) { return; } } catch (Exception e) { // Got exception while trying to retrieve the // given MBean from the underlying MBeanServer // if (JConsole.isDebug()) { e.printStackTrace(); } return; } EventQueue.invokeLater(new Runnable() { public void run() { synchronized (XTree.this) { // Add the new nodes to the MBean tree from leaf to root Dn dn = buildDn(mbean); if (dn.size() == 0) return; Token token = dn.getToken(0); DefaultMutableTreeNode node = null; boolean nodeCreated = true; // // Add the node or replace its user object if already added // String hashKey = dn.getHashKey(token); if (nodes.containsKey(hashKey)) { //already in the tree, means it has been created previously //when adding another node node = nodes.get(hashKey); //sets the user object final Object data = createNodeValue(xmbean, token); final String label = data.toString(); final XNodeInfo userObject = new XNodeInfo(Type.MBEAN, data, label, mbean.toString()); changeNodeValue(node, userObject); nodeCreated = false; } else { //create a new node node = createDnNode(dn, token, xmbean); if (node != null) { nodes.put(hashKey, node); nodeCreated = true; } else { return; } } // // Add (virtual) nodes without user object if necessary // for (int i = 1; i < dn.size(); i++) { DefaultMutableTreeNode currentNode = null; token = dn.getToken(i); hashKey = dn.getHashKey(token); if (nodes.containsKey(hashKey)) { //node already present if (nodeCreated) { //previous node created, link to do currentNode = nodes.get(hashKey); addChildNode(currentNode, node); return; } else { //both nodes already present return; } } else { //creates the node that can be a virtual one if (token.getKeyDn().equals("domain")) { //better match on keyDn that on Dn currentNode = createDomainNode(dn, token); if (currentNode != null) { final DefaultMutableTreeNode root = (DefaultMutableTreeNode) getModel().getRoot(); addChildNode(root, currentNode); } } else { currentNode = createSubDnNode(dn, token); if (currentNode == null) { //skip continue; } } nodes.put(hashKey, currentNode); addChildNode(currentNode, node); nodeCreated = true; } node = currentNode; } } } }); } // Call on EDT private synchronized void changeNodeValue( final DefaultMutableTreeNode node, XNodeInfo nodeValue) { if (node instanceof ComparableDefaultMutableTreeNode) { // should it stay at the same place? DefaultMutableTreeNode clone = (DefaultMutableTreeNode) node.clone(); clone.setUserObject(nodeValue); if (((ComparableDefaultMutableTreeNode) node).compareTo(clone) == 0) { // the order in the tree didn't change node.setUserObject(nodeValue); DefaultTreeModel model = (DefaultTreeModel) getModel(); model.nodeChanged(node); } else { // delete the node and re-order it in case the // node value modifies the order in the tree DefaultMutableTreeNode parent = (DefaultMutableTreeNode) node.getParent(); removeChildNode(node); node.setUserObject(nodeValue); addChildNode(parent, node); } } else { // not comparable stays at the same place node.setUserObject(nodeValue); DefaultTreeModel model = (DefaultTreeModel) getModel(); model.nodeChanged(node); } // Load the MBean metadata if type is MBEAN if (nodeValue.getType().equals(Type.MBEAN)) { XMBeanInfo.loadInfo(node); DefaultTreeModel model = (DefaultTreeModel) getModel(); model.nodeStructureChanged(node); } // Clear the current selection and set it // again so valueChanged() gets called if (node == getLastSelectedPathComponent()) { TreePath selectionPath = getSelectionPath(); clearSelection(); setSelectionPath(selectionPath); } } //creates the domain node, called on a domain token private DefaultMutableTreeNode createDomainNode(Dn dn, Token token) { DefaultMutableTreeNode node = new ComparableDefaultMutableTreeNode(); String label = dn.getDomain(); XNodeInfo userObject = new XNodeInfo(Type.NONMBEAN, label, label, label); node.setUserObject(userObject); return node; } //creates the node corresponding to the whole Dn private DefaultMutableTreeNode createDnNode( Dn dn, Token token, XMBean xmbean) { DefaultMutableTreeNode node = new ComparableDefaultMutableTreeNode(); Object data = createNodeValue(xmbean, token); String label = data.toString(); XNodeInfo userObject = new XNodeInfo(Type.MBEAN, data, label, xmbean.getObjectName().toString()); node.setUserObject(userObject); XMBeanInfo.loadInfo(node); return node; } //creates a node with the token value, call for each non domain sub //dn token private DefaultMutableTreeNode createSubDnNode(Dn dn, Token token) { DefaultMutableTreeNode node = new ComparableDefaultMutableTreeNode(); String label = isKeyValueView() ? token.toString() : token.getValue().toString(); XNodeInfo userObject = new XNodeInfo(Type.NONMBEAN, label, label, token.toString()); node.setUserObject(userObject); return node; } private Object createNodeValue(XMBean xmbean, Token token) { String label = isKeyValueView() ? token.toString() : token.getValue().toString(); xmbean.setText(label); return xmbean; }
Parses MBean ObjectName comma-separated properties string and put the individual key/value pairs into the map. Key order in the properties string is preserved by the map.
/** * Parses MBean ObjectName comma-separated properties string and put the * individual key/value pairs into the map. Key order in the properties * string is preserved by the map. */
private Map<String,String> extractKeyValuePairs( String properties, ObjectName mbean) { String props = properties; Map<String,String> map = new LinkedHashMap<String,String>(); int eq = props.indexOf("="); while (eq != -1) { String key = props.substring(0, eq); String value = mbean.getKeyProperty(key); map.put(key, value); props = props.substring(key.length() + 1 + value.length()); if (props.startsWith(",")) { props = props.substring(1); } eq = props.indexOf("="); } return map; }
Returns the ordered key property list that will be used to build the MBean tree. If the "com.sun.tools.jconsole.mbeans.keyPropertyList" system property is not specified, then the ordered key property list used to build the MBean tree will be the one returned by the method ObjectName.getKeyPropertyListString() with "type" as first key, and "j2eeType" as second key, if present. If any of the keys specified in the comma-separated key property list does not apply to the given MBean then it will be discarded.
/** * Returns the ordered key property list that will be used to build the * MBean tree. If the "com.sun.tools.jconsole.mbeans.keyPropertyList" system * property is not specified, then the ordered key property list used * to build the MBean tree will be the one returned by the method * ObjectName.getKeyPropertyListString() with "type" as first key, * and "j2eeType" as second key, if present. If any of the keys specified * in the comma-separated key property list does not apply to the given * MBean then it will be discarded. */
private String getKeyPropertyListString(ObjectName mbean) { String props = mbean.getKeyPropertyListString(); Map<String,String> map = extractKeyValuePairs(props, mbean); StringBuilder sb = new StringBuilder(); // Add the key/value pairs to the buffer following the // key order defined by the "orderedKeyPropertyList" for (String key : orderedKeyPropertyList) { if (map.containsKey(key)) { sb.append(key + "=" + map.get(key) + ","); map.remove(key); } } // Add the remaining key/value pairs to the buffer for (Map.Entry<String,String> entry : map.entrySet()) { sb.append(entry.getKey() + "=" + entry.getValue() + ","); } String orderedKeyPropertyListString = sb.toString(); orderedKeyPropertyListString = orderedKeyPropertyListString.substring( 0, orderedKeyPropertyListString.length() - 1); return orderedKeyPropertyListString; }
Builds the Dn for the given MBean.
/** * Builds the Dn for the given MBean. */
private Dn buildDn(ObjectName mbean) { String domain = mbean.getDomain(); String globalDn = getKeyPropertyListString(mbean); Dn dn = buildDn(domain, globalDn, mbean); //update the Dn tokens to add the domain dn.updateDn(); //reverse the Dn (from leaf to root) dn.reverseOrder(); //compute the hashDn dn.computeHashDn(); return dn; }
Builds the Dn for the given MBean.
/** * Builds the Dn for the given MBean. */
private Dn buildDn(String domain, String globalDn, ObjectName mbean) { Dn dn = new Dn(domain, globalDn); String keyDn = "no_key"; if (isTreeView()) { String props = globalDn; Map<String,String> map = extractKeyValuePairs(props, mbean); for (Map.Entry<String,String> entry : map.entrySet()) { dn.addToken(new Token(keyDn, entry.getKey() + "=" + entry.getValue())); } } else { //flat view dn.addToken(new Token(keyDn, "properties=" + globalDn)); } return dn; } // //utility objects // public static class ComparableDefaultMutableTreeNode extends DefaultMutableTreeNode implements Comparable<DefaultMutableTreeNode> { public int compareTo(DefaultMutableTreeNode node) { return (this.toString().compareTo(node.toString())); } } // //tree preferences // private boolean treeView; private boolean treeViewInit = false; public boolean isTreeView() { if (!treeViewInit) { treeView = getTreeViewValue(); treeViewInit = true; } return treeView; } private boolean getTreeViewValue() { String treeView = System.getProperty("treeView"); return ((treeView == null) ? true : !(treeView.equals("false"))); } // //MBean key-value preferences // private boolean keyValueView = Boolean.getBoolean("keyValueView"); public boolean isKeyValueView() { return keyValueView; } // //utility classes // public static class Dn { private String domain; private String dn; private String hashDn; private ArrayList<Token> tokens = new ArrayList<Token>(); public Dn(String domain, String dn) { this.domain = domain; this.dn = dn; } public void clearTokens() { tokens.clear(); } public void addToken(Token token) { tokens.add(token); } public void addToken(int index, Token token) { tokens.add(index, token); } public void setToken(int index, Token token) { tokens.set(index, token); } public void removeToken(int index) { tokens.remove(index); } public Token getToken(int index) { return tokens.get(index); } public void reverseOrder() { ArrayList<Token> newOrder = new ArrayList<Token>(tokens.size()); for (int i = tokens.size() - 1; i >= 0; i--) { newOrder.add(tokens.get(i)); } tokens = newOrder; } public int size() { return tokens.size(); } public String getDomain() { return domain; } public String getDn() { return dn; } public String getHashDn() { return hashDn; } public String getHashKey(Token token) { final int begin = getHashDn().indexOf(token.getHashToken()); return getHashDn().substring(begin, getHashDn().length()); } public void computeHashDn() { final StringBuilder hashDn = new StringBuilder(); final int tokensSize = tokens.size(); for (int i = 0; i < tokensSize; i++) { Token token = tokens.get(i); String hashToken = token.getHashToken(); if (hashToken == null) { hashToken = token.getToken() + (tokensSize - i); token.setHashToken(hashToken); } hashDn.append(hashToken); hashDn.append(","); } if (tokensSize > 0) { this.hashDn = hashDn.substring(0, hashDn.length() - 1); } else { this.hashDn = ""; } }
Adds the domain as the first token in the Dn.
/** * Adds the domain as the first token in the Dn. */
public void updateDn() { addToken(0, new Token("domain", "domain=" + getDomain())); } public String toString() { return tokens.toString(); } } public static class Token { private String keyDn; private String token; private String hashToken; private String key; private String value; public Token(String keyDn, String token) { this.keyDn = keyDn; this.token = token; buildKeyValue(); } public Token(String keyDn, String token, String hashToken) { this.keyDn = keyDn; this.token = token; this.hashToken = hashToken; buildKeyValue(); } public String getKeyDn() { return keyDn; } public String getToken() { return token; } public void setValue(String value) { this.value = value; this.token = key + "=" + value; } public void setKey(String key) { this.key = key; this.token = key + "=" + value; } public void setKeyDn(String keyDn) { this.keyDn = keyDn; } public void setHashToken(String hashToken) { this.hashToken = hashToken; } public String getHashToken() { return hashToken; } public String getKey() { return key; } public String getValue() { return value; } public String toString(){ return getToken(); } public boolean equals(Object object) { if (object instanceof Token) { return token.equals(((Token) object)); } else { return false; } } private void buildKeyValue() { int index = token.indexOf("="); if (index < 0) { key = token; value = token; } else { key = token.substring(0, index); value = token.substring(index + 1, token.length()); } } } }