1/*
2 * Copyright (c) 2004, 2012, 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
26package sun.tools.jconsole.inspector;
27
28import java.io.IOException;
29import java.util.*;
30import javax.management.*;
31import javax.swing.*;
32import javax.swing.tree.*;
33import sun.tools.jconsole.JConsole;
34import sun.tools.jconsole.MBeansTab;
35import sun.tools.jconsole.Messages;
36import sun.tools.jconsole.inspector.XNodeInfo;
37import static sun.tools.jconsole.inspector.XNodeInfo.Type;
38
39@SuppressWarnings("serial")
40public 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).append('=').append(map.get(key)).append(',');
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()).append('=').append(entry.getValue()).append(',');
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()).append(',');
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}
835