1 /*
   2  * Copyright (c) 2004, 2008, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package sun.tools.jconsole.inspector;
  27 
  28 import java.io.IOException;
  29 import java.util.*;
  30 import javax.management.*;
  31 import javax.swing.*;
  32 import javax.swing.tree.*;
  33 import sun.tools.jconsole.JConsole;
  34 import sun.tools.jconsole.MBeansTab;
  35 import sun.tools.jconsole.inspector.XNodeInfo;
  36 import sun.tools.jconsole.resources.Messages;
  37 import static sun.tools.jconsole.inspector.XNodeInfo.Type;
  38 
  39 @SuppressWarnings("serial")
  40 public class XTree extends JTree {
  41 
  42     private static final List<String> orderedKeyPropertyList =
  43             new ArrayList<String>();
  44 
  45     static {
  46         String keyPropertyList =
  47                 System.getProperty("com.sun.tools.jconsole.mbeans.keyPropertyList");
  48         if (keyPropertyList == null) {
  49             orderedKeyPropertyList.add("type");
  50             orderedKeyPropertyList.add("j2eeType");
  51         } else {
  52             StringTokenizer st = new StringTokenizer(keyPropertyList, ",");
  53             while (st.hasMoreTokens()) {
  54                 orderedKeyPropertyList.add(st.nextToken());
  55             }
  56         }
  57     }
  58     private MBeansTab mbeansTab;
  59     private Map<String, DefaultMutableTreeNode> nodes =
  60             new HashMap<String, DefaultMutableTreeNode>();
  61 
  62     public XTree(MBeansTab mbeansTab) {
  63         this(new DefaultMutableTreeNode("MBeanTreeRootNode"), mbeansTab);
  64     }
  65 
  66     public XTree(TreeNode root, MBeansTab mbeansTab) {
  67         super(root, true);
  68         this.mbeansTab = mbeansTab;
  69         setRootVisible(false);
  70         setShowsRootHandles(true);
  71         ToolTipManager.sharedInstance().registerComponent(this);
  72     }
  73 
  74     /**
  75      * This method removes the node from its parent
  76      */
  77     // Call on EDT
  78     private synchronized void removeChildNode(DefaultMutableTreeNode child) {
  79         DefaultTreeModel model = (DefaultTreeModel) getModel();
  80         model.removeNodeFromParent(child);
  81     }
  82 
  83     /**
  84      * This method adds the child to the specified parent node
  85      * at specific index.
  86      */
  87     // Call on EDT
  88     private synchronized void addChildNode(
  89             DefaultMutableTreeNode parent,
  90             DefaultMutableTreeNode child,
  91             int index) {
  92         DefaultTreeModel model = (DefaultTreeModel) getModel();
  93         model.insertNodeInto(child, parent, index);
  94     }
  95 
  96     /**
  97      * This method adds the child to the specified parent node.
  98      * The index where the child is to be added depends on the
  99      * child node being Comparable or not. If the child node is
 100      * not Comparable then it is added at the end, i.e. right
 101      * after the current parent's children.
 102      */
 103     // Call on EDT
 104     private synchronized void addChildNode(
 105             DefaultMutableTreeNode parent, DefaultMutableTreeNode child) {
 106         int childCount = parent.getChildCount();
 107         if (childCount == 0) {
 108             addChildNode(parent, child, 0);
 109             return;
 110         }
 111         if (child instanceof ComparableDefaultMutableTreeNode) {
 112             ComparableDefaultMutableTreeNode comparableChild =
 113                     (ComparableDefaultMutableTreeNode) child;
 114             for (int i = childCount - 1; i >= 0; i--) {
 115                 DefaultMutableTreeNode brother =
 116                         (DefaultMutableTreeNode) parent.getChildAt(i);
 117                 // expr1: child node must be inserted after metadata nodes
 118                 // - OR -
 119                 // expr2: "child >= brother"
 120                 if ((i <= 2 && isMetadataNode(brother)) ||
 121                         comparableChild.compareTo(brother) >= 0) {
 122                     addChildNode(parent, child, i + 1);
 123                     return;
 124                 }
 125             }
 126             // "child < all brothers", add at the beginning
 127             addChildNode(parent, child, 0);
 128             return;
 129         }
 130         // "child not comparable", add at the end
 131         addChildNode(parent, child, childCount);
 132     }
 133 
 134     /**
 135      * This method removes all the displayed nodes from the tree,
 136      * but does not affect actual MBeanServer contents.
 137      */
 138     // Call on EDT
 139     @Override
 140     public synchronized void removeAll() {
 141         DefaultTreeModel model = (DefaultTreeModel) getModel();
 142         DefaultMutableTreeNode root = (DefaultMutableTreeNode) model.getRoot();
 143         root.removeAllChildren();
 144         model.nodeStructureChanged(root);
 145         nodes.clear();
 146     }
 147 
 148     // Call on EDT
 149     public synchronized void removeMBeanFromView(ObjectName mbean) {
 150         // We assume here that MBeans are removed one by one (on MBean
 151         // unregistered notification). Deletes the tree node associated
 152         // with the given MBean and recursively all the node parents
 153         // which are leaves and non XMBean.
 154         //
 155         DefaultMutableTreeNode node = null;
 156         Dn dn = new Dn(mbean);
 157         if (dn.getTokenCount() > 0) {
 158             DefaultTreeModel model = (DefaultTreeModel) getModel();
 159             Token token = dn.getToken(0);
 160             String hashKey = dn.getHashKey(token);
 161             node = nodes.get(hashKey);
 162             if ((node != null) && (!node.isRoot())) {
 163                 if (hasNonMetadataNodes(node)) {
 164                     removeMetadataNodes(node);
 165                     String label = token.getValue();
 166                     XNodeInfo userObject = new XNodeInfo(
 167                             Type.NONMBEAN, label,
 168                             label, token.getTokenValue());
 169                     changeNodeValue(node, userObject);
 170                 } else {
 171                     DefaultMutableTreeNode parent =
 172                             (DefaultMutableTreeNode) node.getParent();
 173                     model.removeNodeFromParent(node);
 174                     nodes.remove(hashKey);
 175                     removeParentFromView(dn, 1, parent);
 176                 }
 177             }
 178         }
 179     }
 180 
 181     /**
 182      * Returns true if any of the children nodes is a non MBean metadata node.
 183      */
 184     private boolean hasNonMetadataNodes(DefaultMutableTreeNode node) {
 185         for (Enumeration<?> e = node.children(); e.hasMoreElements();) {
 186             DefaultMutableTreeNode n = (DefaultMutableTreeNode) e.nextElement();
 187             Object uo = n.getUserObject();
 188             if (uo instanceof XNodeInfo) {
 189                 switch (((XNodeInfo) uo).getType()) {
 190                     case ATTRIBUTES:
 191                     case NOTIFICATIONS:
 192                     case OPERATIONS:
 193                         break;
 194                     default:
 195                         return true;
 196                 }
 197             } else {
 198                 return true;
 199             }
 200         }
 201         return false;
 202     }
 203 
 204     /**
 205      * Returns true if any of the children nodes is an MBean metadata node.
 206      */
 207     public boolean hasMetadataNodes(DefaultMutableTreeNode node) {
 208         for (Enumeration<?> e = node.children(); e.hasMoreElements();) {
 209             DefaultMutableTreeNode n = (DefaultMutableTreeNode) e.nextElement();
 210             Object uo = n.getUserObject();
 211             if (uo instanceof XNodeInfo) {
 212                 switch (((XNodeInfo) uo).getType()) {
 213                     case ATTRIBUTES:
 214                     case NOTIFICATIONS:
 215                     case OPERATIONS:
 216                         return true;
 217                     default:
 218                         break;
 219                 }
 220             } else {
 221                 return false;
 222             }
 223         }
 224         return false;
 225     }
 226 
 227     /**
 228      * Returns true if the given node is an MBean metadata node.
 229      */
 230     public boolean isMetadataNode(DefaultMutableTreeNode node) {
 231         Object uo = node.getUserObject();
 232         if (uo instanceof XNodeInfo) {
 233             switch (((XNodeInfo) uo).getType()) {
 234                 case ATTRIBUTES:
 235                 case NOTIFICATIONS:
 236                 case OPERATIONS:
 237                     return true;
 238                 default:
 239                     return false;
 240             }
 241         } else {
 242             return false;
 243         }
 244     }
 245 
 246     /**
 247      * Remove the metadata nodes associated with a given MBean node.
 248      */
 249     // Call on EDT
 250     private void removeMetadataNodes(DefaultMutableTreeNode node) {
 251         Set<DefaultMutableTreeNode> metadataNodes =
 252                 new HashSet<DefaultMutableTreeNode>();
 253         DefaultTreeModel model = (DefaultTreeModel) getModel();
 254         for (Enumeration<?> e = node.children(); e.hasMoreElements();) {
 255             DefaultMutableTreeNode n = (DefaultMutableTreeNode) e.nextElement();
 256             Object uo = n.getUserObject();
 257             if (uo instanceof XNodeInfo) {
 258                 switch (((XNodeInfo) uo).getType()) {
 259                     case ATTRIBUTES:
 260                     case NOTIFICATIONS:
 261                     case OPERATIONS:
 262                         metadataNodes.add(n);
 263                         break;
 264                     default:
 265                         break;
 266                 }
 267             }
 268         }
 269         for (DefaultMutableTreeNode n : metadataNodes) {
 270             model.removeNodeFromParent(n);
 271         }
 272     }
 273 
 274     /**
 275      * Removes only the parent nodes which are non MBean and leaf.
 276      * This method assumes the child nodes have been removed before.
 277      */
 278     // Call on EDT
 279     private DefaultMutableTreeNode removeParentFromView(
 280             Dn dn, int index, DefaultMutableTreeNode node) {
 281         if ((!node.isRoot()) && node.isLeaf() &&
 282                 (!(((XNodeInfo) node.getUserObject()).getType().equals(Type.MBEAN)))) {
 283             DefaultMutableTreeNode parent =
 284                     (DefaultMutableTreeNode) node.getParent();
 285             removeChildNode(node);
 286             String hashKey = dn.getHashKey(dn.getToken(index));
 287             nodes.remove(hashKey);
 288             removeParentFromView(dn, index + 1, parent);
 289         }
 290         return node;
 291     }
 292 
 293     // Call on EDT
 294     public synchronized void addMBeansToView(Set<ObjectName> mbeans) {
 295         Set<Dn> dns = new TreeSet<Dn>();
 296         for (ObjectName mbean : mbeans) {
 297             Dn dn = new Dn(mbean);
 298             dns.add(dn);
 299         }
 300         for (Dn dn : dns) {
 301             ObjectName mbean = dn.getObjectName();
 302             XMBean xmbean = new XMBean(mbean, mbeansTab);
 303             addMBeanToView(mbean, xmbean, dn);
 304         }
 305     }
 306 
 307     // Call on EDT
 308     public synchronized void addMBeanToView(ObjectName mbean) {
 309         // Build XMBean for the given MBean
 310         //
 311         XMBean xmbean = new XMBean(mbean, mbeansTab);
 312         // Build Dn for the given MBean
 313         //
 314         Dn dn = new Dn(mbean);
 315         // Add the new nodes to the MBean tree from leaf to root
 316         //
 317         addMBeanToView(mbean, xmbean, dn);
 318     }
 319 
 320     // Call on EDT
 321     private synchronized void addMBeanToView(
 322             ObjectName mbean, XMBean xmbean, Dn dn) {
 323 
 324         DefaultMutableTreeNode childNode = null;
 325         DefaultMutableTreeNode parentNode = null;
 326 
 327         // Add the node or replace its user object if already added
 328         //
 329         Token token = dn.getToken(0);
 330         String hashKey = dn.getHashKey(token);
 331         if (nodes.containsKey(hashKey)) {
 332             // Found existing node previously created when adding another node
 333             //
 334             childNode = nodes.get(hashKey);
 335             // Replace user object to reflect that this node is an MBean
 336             //
 337             Object data = createNodeValue(xmbean, token);
 338             String label = data.toString();
 339             XNodeInfo userObject =
 340                     new XNodeInfo(Type.MBEAN, data, label, mbean.toString());
 341             changeNodeValue(childNode, userObject);
 342             return;
 343         }
 344 
 345         // Create new leaf node
 346         //
 347         childNode = createDnNode(dn, token, xmbean);
 348         nodes.put(hashKey, childNode);
 349 
 350         // Add intermediate non MBean nodes
 351         //
 352         for (int i = 1; i < dn.getTokenCount(); i++) {
 353             token = dn.getToken(i);
 354             hashKey = dn.getHashKey(token);
 355             if (nodes.containsKey(hashKey)) {
 356                 // Intermediate node already present, add new node as child
 357                 //
 358                 parentNode = nodes.get(hashKey);
 359                 addChildNode(parentNode, childNode);
 360                 return;
 361             } else {
 362                 // Create new intermediate node
 363                 //
 364                 if ("domain".equals(token.getTokenType())) {
 365                     parentNode = createDomainNode(dn, token);
 366                     DefaultMutableTreeNode root =
 367                             (DefaultMutableTreeNode) getModel().getRoot();
 368                     addChildNode(root, parentNode);
 369                 } else {
 370                     parentNode = createSubDnNode(dn, token);
 371                 }
 372                 nodes.put(hashKey, parentNode);
 373                 addChildNode(parentNode, childNode);
 374             }
 375             childNode = parentNode;
 376         }
 377     }
 378 
 379     // Call on EDT
 380     private synchronized void changeNodeValue(
 381             DefaultMutableTreeNode node, XNodeInfo nodeValue) {
 382         if (node instanceof ComparableDefaultMutableTreeNode) {
 383             // should it stay at the same place?
 384             DefaultMutableTreeNode clone =
 385                     (DefaultMutableTreeNode) node.clone();
 386             clone.setUserObject(nodeValue);
 387             if (((ComparableDefaultMutableTreeNode) node).compareTo(clone) == 0) {
 388                 // the order in the tree didn't change
 389                 node.setUserObject(nodeValue);
 390                 DefaultTreeModel model = (DefaultTreeModel) getModel();
 391                 model.nodeChanged(node);
 392             } else {
 393                 // delete the node and re-order it in case the
 394                 // node value modifies the order in the tree
 395                 DefaultMutableTreeNode parent =
 396                         (DefaultMutableTreeNode) node.getParent();
 397                 removeChildNode(node);
 398                 node.setUserObject(nodeValue);
 399                 addChildNode(parent, node);
 400             }
 401         } else {
 402             // not comparable stays at the same place
 403             node.setUserObject(nodeValue);
 404             DefaultTreeModel model = (DefaultTreeModel) getModel();
 405             model.nodeChanged(node);
 406         }
 407         // Load the MBean metadata if type is MBEAN
 408         if (nodeValue.getType().equals(Type.MBEAN)) {
 409             removeMetadataNodes(node);
 410             TreeNode[] treeNodes = node.getPath();
 411             TreePath path = new TreePath(treeNodes);
 412             if (isExpanded(path)) {
 413                 addMetadataNodes(node);
 414             }
 415         }
 416         // Clear the current selection and set it
 417         // again so valueChanged() gets called
 418         if (node == getLastSelectedPathComponent()) {
 419             TreePath selectionPath = getSelectionPath();
 420             clearSelection();
 421             setSelectionPath(selectionPath);
 422         }
 423     }
 424 
 425     /**
 426      * Creates the domain node.
 427      */
 428     private DefaultMutableTreeNode createDomainNode(Dn dn, Token token) {
 429         DefaultMutableTreeNode node = new ComparableDefaultMutableTreeNode();
 430         String label = dn.getDomain();
 431         XNodeInfo userObject =
 432                 new XNodeInfo(Type.NONMBEAN, label, label, label);
 433         node.setUserObject(userObject);
 434         return node;
 435     }
 436 
 437     /**
 438      * Creates the node corresponding to the whole Dn, i.e. an MBean.
 439      */
 440     private DefaultMutableTreeNode createDnNode(
 441             Dn dn, Token token, XMBean xmbean) {
 442         DefaultMutableTreeNode node = new ComparableDefaultMutableTreeNode();
 443         Object data = createNodeValue(xmbean, token);
 444         String label = data.toString();
 445         XNodeInfo userObject = new XNodeInfo(Type.MBEAN, data, label,
 446                 xmbean.getObjectName().toString());
 447         node.setUserObject(userObject);
 448         return node;
 449     }
 450 
 451     /**
 452      * Creates the node corresponding to a subDn, i.e. a non-MBean
 453      * intermediate node.
 454      */
 455     private DefaultMutableTreeNode createSubDnNode(Dn dn, Token token) {
 456         DefaultMutableTreeNode node = new ComparableDefaultMutableTreeNode();
 457         String label = isKeyValueView() ? token.getTokenValue() : token.getValue();
 458         XNodeInfo userObject =
 459                 new XNodeInfo(Type.NONMBEAN, label, label, token.getTokenValue());
 460         node.setUserObject(userObject);
 461         return node;
 462     }
 463 
 464     private Object createNodeValue(XMBean xmbean, Token token) {
 465         String label = isKeyValueView() ? token.getTokenValue() : token.getValue();
 466         xmbean.setText(label);
 467         return xmbean;
 468     }
 469 
 470     /**
 471      * Parses the MBean ObjectName comma-separated properties string and puts
 472      * the individual key/value pairs into the map. Key order in the properties
 473      * string is preserved by the map.
 474      */
 475     private static Map<String, String> extractKeyValuePairs(
 476             String props, ObjectName mbean) {
 477         Map<String, String> map = new LinkedHashMap<String, String>();
 478         int eq = props.indexOf("=");
 479         while (eq != -1) {
 480             String key = props.substring(0, eq);
 481             String value = mbean.getKeyProperty(key);
 482             map.put(key, value);
 483             props = props.substring(key.length() + 1 + value.length());
 484             if (props.startsWith(",")) {
 485                 props = props.substring(1);
 486             }
 487             eq = props.indexOf("=");
 488         }
 489         return map;
 490     }
 491 
 492     /**
 493      * Returns the ordered key property list that will be used to build the
 494      * MBean tree. If the "com.sun.tools.jconsole.mbeans.keyPropertyList" system
 495      * property is not specified, then the ordered key property list used
 496      * to build the MBean tree will be the one returned by the method
 497      * ObjectName.getKeyPropertyListString() with "type" as first key,
 498      * and "j2eeType" as second key, if present. If any of the keys specified
 499      * in the comma-separated key property list does not apply to the given
 500      * MBean then it will be discarded.
 501      */
 502     private static String getKeyPropertyListString(ObjectName mbean) {
 503         String props = mbean.getKeyPropertyListString();
 504         Map<String, String> map = extractKeyValuePairs(props, mbean);
 505         StringBuilder sb = new StringBuilder();
 506         // Add the key/value pairs to the buffer following the
 507         // key order defined by the "orderedKeyPropertyList"
 508         for (String key : orderedKeyPropertyList) {
 509             if (map.containsKey(key)) {
 510                 sb.append(key + "=" + map.get(key) + ",");
 511                 map.remove(key);
 512             }
 513         }
 514         // Add the remaining key/value pairs to the buffer
 515         for (Map.Entry<String, String> entry : map.entrySet()) {
 516             sb.append(entry.getKey() + "=" + entry.getValue() + ",");
 517         }
 518         String orderedKeyPropertyListString = sb.toString();
 519         orderedKeyPropertyListString = orderedKeyPropertyListString.substring(
 520                 0, orderedKeyPropertyListString.length() - 1);
 521         return orderedKeyPropertyListString;
 522     }
 523 
 524     // Call on EDT
 525     public void addMetadataNodes(DefaultMutableTreeNode node) {
 526         XMBean mbean = (XMBean) ((XNodeInfo) node.getUserObject()).getData();
 527         DefaultTreeModel model = (DefaultTreeModel) getModel();
 528         MBeanInfoNodesSwingWorker sw =
 529                 new MBeanInfoNodesSwingWorker(model, node, mbean);
 530         if (sw != null) {
 531             sw.execute();
 532         }
 533     }
 534 
 535     private static class MBeanInfoNodesSwingWorker
 536             extends SwingWorker<Object[], Void> {
 537 
 538         private final DefaultTreeModel model;
 539         private final DefaultMutableTreeNode node;
 540         private final XMBean mbean;
 541 
 542         public MBeanInfoNodesSwingWorker(
 543                 DefaultTreeModel model,
 544                 DefaultMutableTreeNode node,
 545                 XMBean mbean) {
 546             this.model = model;
 547             this.node = node;
 548             this.mbean = mbean;
 549         }
 550 
 551         @Override
 552         public Object[] doInBackground() throws InstanceNotFoundException,
 553                 IntrospectionException, ReflectionException, IOException {
 554             Object result[] = new Object[2];
 555             // Retrieve MBeanInfo for this MBean
 556             result[0] = mbean.getMBeanInfo();
 557             // Check if this MBean is a notification emitter
 558             result[1] = mbean.isBroadcaster();
 559             return result;
 560         }
 561 
 562         @Override
 563         protected void done() {
 564             try {
 565                 Object result[] = get();
 566                 MBeanInfo mbeanInfo = (MBeanInfo) result[0];
 567                 Boolean isBroadcaster = (Boolean) result[1];
 568                 if (mbeanInfo != null) {
 569                     addMBeanInfoNodes(model, node, mbean, mbeanInfo, isBroadcaster);
 570                 }
 571             } catch (Exception e) {
 572                 Throwable t = Utils.getActualException(e);
 573                 if (JConsole.isDebug()) {
 574                     t.printStackTrace();
 575                 }
 576             }
 577         }
 578 
 579         // Call on EDT
 580         private void addMBeanInfoNodes(
 581                 DefaultTreeModel tree, DefaultMutableTreeNode node,
 582                 XMBean mbean, MBeanInfo mbeanInfo, Boolean isBroadcaster) {
 583             MBeanAttributeInfo[] ai = mbeanInfo.getAttributes();
 584             MBeanOperationInfo[] oi = mbeanInfo.getOperations();
 585             MBeanNotificationInfo[] ni = mbeanInfo.getNotifications();
 586 
 587             // Insert the Attributes/Operations/Notifications metadata nodes as
 588             // the three first children of this MBean node. This is only useful
 589             // when this MBean node denotes an MBean but it's not a leaf in the
 590             // MBean tree
 591             //
 592             int childIndex = 0;
 593 
 594             // MBeanAttributeInfo node
 595             //
 596             if (ai != null && ai.length > 0) {
 597                 DefaultMutableTreeNode attributes = new DefaultMutableTreeNode();
 598                 XNodeInfo attributesUO = new XNodeInfo(Type.ATTRIBUTES, mbean,
 599                         Messages.ATTRIBUTES, null);
 600                 attributes.setUserObject(attributesUO);
 601                 node.insert(attributes, childIndex++);
 602                 for (MBeanAttributeInfo mbai : ai) {
 603                     DefaultMutableTreeNode attribute = new DefaultMutableTreeNode();
 604                     XNodeInfo attributeUO = new XNodeInfo(Type.ATTRIBUTE,
 605                             new Object[]{mbean, mbai}, mbai.getName(), null);
 606                     attribute.setUserObject(attributeUO);
 607                     attribute.setAllowsChildren(false);
 608                     attributes.add(attribute);
 609                 }
 610             }
 611             // MBeanOperationInfo node
 612             //
 613             if (oi != null && oi.length > 0) {
 614                 DefaultMutableTreeNode operations = new DefaultMutableTreeNode();
 615                 XNodeInfo operationsUO = new XNodeInfo(Type.OPERATIONS, mbean,
 616                         Messages.OPERATIONS, null);
 617                 operations.setUserObject(operationsUO);
 618                 node.insert(operations, childIndex++);
 619                 for (MBeanOperationInfo mboi : oi) {
 620                     // Compute the operation's tool tip text:
 621                     // "operationname(param1type,param2type,...)"
 622                     //
 623                     StringBuilder sb = new StringBuilder();
 624                     for (MBeanParameterInfo mbpi : mboi.getSignature()) {
 625                         sb.append(mbpi.getType() + ",");
 626                     }
 627                     String signature = sb.toString();
 628                     if (signature.length() > 0) {
 629                         // Remove the trailing ','
 630                         //
 631                         signature = signature.substring(0, signature.length() - 1);
 632                     }
 633                     String toolTipText = mboi.getName() + "(" + signature + ")";
 634                     // Create operation node
 635                     //
 636                     DefaultMutableTreeNode operation = new DefaultMutableTreeNode();
 637                     XNodeInfo operationUO = new XNodeInfo(Type.OPERATION,
 638                             new Object[]{mbean, mboi}, mboi.getName(), toolTipText);
 639                     operation.setUserObject(operationUO);
 640                     operation.setAllowsChildren(false);
 641                     operations.add(operation);
 642                 }
 643             }
 644             // MBeanNotificationInfo node
 645             //
 646             if (isBroadcaster != null && isBroadcaster.booleanValue()) {
 647                 DefaultMutableTreeNode notifications = new DefaultMutableTreeNode();
 648                 XNodeInfo notificationsUO = new XNodeInfo(Type.NOTIFICATIONS, mbean,
 649                         Messages.NOTIFICATIONS, null);
 650                 notifications.setUserObject(notificationsUO);
 651                 node.insert(notifications, childIndex++);
 652                 if (ni != null && ni.length > 0) {
 653                     for (MBeanNotificationInfo mbni : ni) {
 654                         DefaultMutableTreeNode notification =
 655                                 new DefaultMutableTreeNode();
 656                         XNodeInfo notificationUO = new XNodeInfo(Type.NOTIFICATION,
 657                                 mbni, mbni.getName(), null);
 658                         notification.setUserObject(notificationUO);
 659                         notification.setAllowsChildren(false);
 660                         notifications.add(notification);
 661                     }
 662                 }
 663             }
 664             // Update tree model
 665             //
 666             model.reload(node);
 667         }
 668     }
 669     //
 670     // Tree preferences
 671     //
 672     private static boolean treeView;
 673     private static boolean treeViewInit = false;
 674 
 675     private static boolean isTreeView() {
 676         if (!treeViewInit) {
 677             treeView = getTreeViewValue();
 678             treeViewInit = true;
 679         }
 680         return treeView;
 681     }
 682 
 683     private static boolean getTreeViewValue() {
 684         String tv = System.getProperty("treeView");
 685         return ((tv == null) ? true : !(tv.equals("false")));
 686     }
 687     //
 688     // MBean key-value preferences
 689     //
 690     private boolean keyValueView = Boolean.getBoolean("keyValueView");
 691 
 692     private boolean isKeyValueView() {
 693         return keyValueView;
 694     }
 695 
 696     //
 697     // Utility classes
 698     //
 699     private static class ComparableDefaultMutableTreeNode
 700             extends DefaultMutableTreeNode
 701             implements Comparable<DefaultMutableTreeNode> {
 702 
 703         public int compareTo(DefaultMutableTreeNode node) {
 704             return (this.toString().compareTo(node.toString()));
 705         }
 706     }
 707 
 708     private static class Dn implements Comparable<Dn> {
 709 
 710         private ObjectName mbean;
 711         private String domain;
 712         private String keyPropertyList;
 713         private String hashDn;
 714         private List<Token> tokens = new ArrayList<Token>();
 715 
 716         public Dn(ObjectName mbean) {
 717             this.mbean = mbean;
 718             this.domain = mbean.getDomain();
 719             this.keyPropertyList = getKeyPropertyListString(mbean);
 720 
 721             if (isTreeView()) {
 722                 // Tree view
 723                 Map<String, String> map =
 724                         extractKeyValuePairs(keyPropertyList, mbean);
 725                 for (Map.Entry<String, String> entry : map.entrySet()) {
 726                     tokens.add(new Token("key", entry.getKey() + "=" + entry.getValue()));
 727                 }
 728             } else {
 729                 // Flat view
 730                 tokens.add(new Token("key", "properties=" + keyPropertyList));
 731             }
 732 
 733             // Add the domain as the first token in the Dn
 734             tokens.add(0, new Token("domain", "domain=" + domain));
 735 
 736             // Reverse the Dn (from leaf to root)
 737             Collections.reverse(tokens);
 738 
 739             // Compute hash for Dn
 740             computeHashDn();
 741         }
 742 
 743         public ObjectName getObjectName() {
 744             return mbean;
 745         }
 746 
 747         public String getDomain() {
 748             return domain;
 749         }
 750 
 751         public String getKeyPropertyList() {
 752             return keyPropertyList;
 753         }
 754 
 755         public Token getToken(int index) {
 756             return tokens.get(index);
 757         }
 758 
 759         public int getTokenCount() {
 760             return tokens.size();
 761         }
 762 
 763         public String getHashDn() {
 764             return hashDn;
 765         }
 766 
 767         public String getHashKey(Token token) {
 768             final int begin = hashDn.indexOf(token.getTokenValue());
 769             return hashDn.substring(begin, hashDn.length());
 770         }
 771 
 772         private void computeHashDn() {
 773             if (tokens.isEmpty()) {
 774                 return;
 775             }
 776             final StringBuilder hdn = new StringBuilder();
 777             for (int i = 0; i < tokens.size(); i++) {
 778                 hdn.append(tokens.get(i).getTokenValue());
 779                 hdn.append(",");
 780             }
 781             hashDn = hdn.substring(0, hdn.length() - 1);
 782         }
 783 
 784         @Override
 785         public String toString() {
 786             return domain + ":" + keyPropertyList;
 787         }
 788 
 789         public int compareTo(Dn dn) {
 790             return this.toString().compareTo(dn.toString());
 791         }
 792     }
 793 
 794     private static class Token {
 795 
 796         private String tokenType;
 797         private String tokenValue;
 798         private String key;
 799         private String value;
 800 
 801         public Token(String tokenType, String tokenValue) {
 802             this.tokenType = tokenType;
 803             this.tokenValue = tokenValue;
 804             buildKeyValue();
 805         }
 806 
 807         public String getTokenType() {
 808             return tokenType;
 809         }
 810 
 811         public String getTokenValue() {
 812             return tokenValue;
 813         }
 814 
 815         public String getKey() {
 816             return key;
 817         }
 818 
 819         public String getValue() {
 820             return value;
 821         }
 822 
 823         private void buildKeyValue() {
 824             int index = tokenValue.indexOf("=");
 825             if (index < 0) {
 826                 key = tokenValue;
 827                 value = tokenValue;
 828             } else {
 829                 key = tokenValue.substring(0, index);
 830                 value = tokenValue.substring(index + 1, tokenValue.length());
 831             }
 832         }
 833     }
 834 }