001: /*
002: * Copyright 2004-2006 Sun Microsystems, Inc. All Rights Reserved.
003: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004: *
005: * This code is free software; you can redistribute it and/or modify it
006: * under the terms of the GNU General Public License version 2 only, as
007: * published by the Free Software Foundation. Sun designates this
008: * particular file as subject to the "Classpath" exception as provided
009: * by Sun in the LICENSE file that accompanied this code.
010: *
011: * This code is distributed in the hope that it will be useful, but WITHOUT
012: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014: * version 2 for more details (a copy is included in the LICENSE file that
015: * accompanied this code).
016: *
017: * You should have received a copy of the GNU General Public License version
018: * 2 along with this work; if not, write to the Free Software Foundation,
019: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020: *
021: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022: * CA 95054 USA or visit www.sun.com if you need additional information or
023: * have any questions.
024: */
025:
026: package sun.tools.jconsole.inspector;
027:
028: import java.awt.EventQueue;
029: import java.util.*;
030: import javax.management.*;
031: import javax.swing.*;
032: import javax.swing.tree.*;
033: import sun.tools.jconsole.JConsole;
034: import sun.tools.jconsole.MBeansTab;
035: import sun.tools.jconsole.Resources;
036: import sun.tools.jconsole.inspector.XNodeInfo;
037: import sun.tools.jconsole.inspector.XNodeInfo.Type;
038:
039: @SuppressWarnings("serial")
040: public class XTree extends JTree {
041:
042: private static final List<String> orderedKeyPropertyList = new ArrayList<String>();
043: static {
044: String keyPropertyList = System
045: .getProperty("com.sun.tools.jconsole.mbeans.keyPropertyList");
046: if (keyPropertyList == null) {
047: orderedKeyPropertyList.add("type");
048: orderedKeyPropertyList.add("j2eeType");
049: } else {
050: StringTokenizer st = new StringTokenizer(keyPropertyList,
051: ",");
052: while (st.hasMoreTokens()) {
053: orderedKeyPropertyList.add(st.nextToken());
054: }
055: }
056: }
057:
058: private MBeansTab mbeansTab;
059:
060: private Map<String, DefaultMutableTreeNode> nodes = new HashMap<String, DefaultMutableTreeNode>();
061:
062: public XTree(MBeansTab mbeansTab) {
063: this (new DefaultMutableTreeNode("MBeanTreeRootNode"), mbeansTab);
064: }
065:
066: public XTree(TreeNode root, MBeansTab mbeansTab) {
067: super (root);
068: this .mbeansTab = mbeansTab;
069: setRootVisible(false);
070: setShowsRootHandles(true);
071: ToolTipManager.sharedInstance().registerComponent(this );
072: }
073:
074: /**
075: * This method removes the node from its parent
076: */
077: // Call on EDT
078: private synchronized void removeChildNode(
079: DefaultMutableTreeNode child) {
080: DefaultTreeModel model = (DefaultTreeModel) getModel();
081: model.removeNodeFromParent(child);
082: }
083:
084: /**
085: * This method adds the child to the specified parent node
086: * at specific index.
087: */
088: // Call on EDT
089: private synchronized void addChildNode(
090: DefaultMutableTreeNode parent,
091: DefaultMutableTreeNode child, int index) {
092: // Tree does not show up when there is only the root node
093: //
094: DefaultTreeModel model = (DefaultTreeModel) getModel();
095: DefaultMutableTreeNode root = (DefaultMutableTreeNode) model
096: .getRoot();
097: boolean rootLeaf = root.isLeaf();
098: model.insertNodeInto(child, parent, index);
099: if (rootLeaf) {
100: model.nodeStructureChanged(root);
101: }
102: }
103:
104: /**
105: * This method adds the child to the specified parent node.
106: * The index where the child is to be added depends on the
107: * child node being Comparable or not. If the child node is
108: * not Comparable then it is added at the end, i.e. right
109: * after the current parent's children.
110: */
111: // Call on EDT
112: private synchronized void addChildNode(
113: DefaultMutableTreeNode parent, DefaultMutableTreeNode child) {
114: int childCount = parent.getChildCount();
115: if (childCount == 0) {
116: addChildNode(parent, child, 0);
117: } else if (child instanceof ComparableDefaultMutableTreeNode) {
118: ComparableDefaultMutableTreeNode comparableChild = (ComparableDefaultMutableTreeNode) child;
119: int i = 0;
120: for (; i < childCount; i++) {
121: DefaultMutableTreeNode brother = (DefaultMutableTreeNode) parent
122: .getChildAt(i);
123: //child < brother
124: if (comparableChild.compareTo(brother) < 0) {
125: addChildNode(parent, child, i);
126: break;
127: }
128: //child = brother
129: else if (comparableChild.compareTo(brother) == 0) {
130: addChildNode(parent, child, i);
131: break;
132: }
133: }
134: //child < all brothers
135: if (i == childCount) {
136: addChildNode(parent, child, childCount);
137: }
138: } else {
139: //not comparable, add at the end
140: addChildNode(parent, child, childCount);
141: }
142: }
143:
144: /**
145: * This method removes all the displayed nodes from the tree,
146: * but does not affect actual MBeanServer contents.
147: */
148: // Call on EDT
149: public synchronized void removeAll() {
150: DefaultTreeModel model = (DefaultTreeModel) getModel();
151: DefaultMutableTreeNode root = (DefaultMutableTreeNode) model
152: .getRoot();
153: root.removeAllChildren();
154: model.nodeStructureChanged(root);
155: nodes.clear();
156: }
157:
158: public void delMBeanFromView(final ObjectName mbean) {
159: EventQueue.invokeLater(new Runnable() {
160: public void run() {
161: // We assume here that MBeans are removed one by one (on MBean
162: // unregistered notification). Deletes the tree node associated
163: // with the given MBean and recursively all the node parents
164: // which are leaves and non XMBean.
165: //
166: synchronized (XTree.this ) {
167: DefaultMutableTreeNode node = null;
168: Dn dn = buildDn(mbean);
169: if (dn.size() > 0) {
170: DefaultTreeModel model = (DefaultTreeModel) getModel();
171: Token token = dn.getToken(0);
172: String hashKey = dn.getHashKey(token);
173: node = nodes.get(hashKey);
174: if ((node != null) && (!node.isRoot())) {
175: if (hasMBeanChildren(node)) {
176: removeNonMBeanChildren(node);
177: String label = token.getValue()
178: .toString();
179: XNodeInfo userObject = new XNodeInfo(
180: Type.NONMBEAN, label, label,
181: token.toString());
182: changeNodeValue(node, userObject);
183: } else {
184: DefaultMutableTreeNode parent = (DefaultMutableTreeNode) node
185: .getParent();
186: model.removeNodeFromParent(node);
187: nodes.remove(hashKey);
188: delParentFromView(dn, 1, parent);
189: }
190: }
191: }
192: }
193: }
194: });
195: }
196:
197: /**
198: * Returns true if any of the children nodes is an MBean.
199: */
200: private boolean hasMBeanChildren(DefaultMutableTreeNode node) {
201: for (Enumeration e = node.children(); e.hasMoreElements();) {
202: DefaultMutableTreeNode n = (DefaultMutableTreeNode) e
203: .nextElement();
204: if (((XNodeInfo) n.getUserObject()).getType().equals(
205: Type.MBEAN)) {
206: return true;
207: }
208: }
209: return false;
210: }
211:
212: /**
213: * Remove all the children nodes which are not MBean.
214: */
215: private void removeNonMBeanChildren(DefaultMutableTreeNode node) {
216: Set<DefaultMutableTreeNode> metadataNodes = new HashSet<DefaultMutableTreeNode>();
217: DefaultTreeModel model = (DefaultTreeModel) getModel();
218: for (Enumeration e = node.children(); e.hasMoreElements();) {
219: DefaultMutableTreeNode n = (DefaultMutableTreeNode) e
220: .nextElement();
221: if (!((XNodeInfo) n.getUserObject()).getType().equals(
222: Type.MBEAN)) {
223: metadataNodes.add(n);
224: }
225: }
226: for (DefaultMutableTreeNode n : metadataNodes) {
227: model.removeNodeFromParent(n);
228: }
229: }
230:
231: /**
232: * Removes only the parent nodes which are non MBean and leaf.
233: * This method assumes the child nodes have been removed before.
234: */
235: private DefaultMutableTreeNode delParentFromView(Dn dn, int index,
236: DefaultMutableTreeNode node) {
237: if ((!node.isRoot())
238: && node.isLeaf()
239: && (!(((XNodeInfo) node.getUserObject()).getType()
240: .equals(Type.MBEAN)))) {
241: DefaultMutableTreeNode parent = (DefaultMutableTreeNode) node
242: .getParent();
243: removeChildNode(node);
244: String hashKey = dn.getHashKey(dn.getToken(index));
245: nodes.remove(hashKey);
246: delParentFromView(dn, index + 1, parent);
247: }
248: return node;
249: }
250:
251: public synchronized void addMBeanToView(final ObjectName mbean) {
252: final XMBean xmbean;
253: try {
254: xmbean = new XMBean(mbean, mbeansTab);
255: if (xmbean == null) {
256: return;
257: }
258: } catch (Exception e) {
259: // Got exception while trying to retrieve the
260: // given MBean from the underlying MBeanServer
261: //
262: if (JConsole.isDebug()) {
263: e.printStackTrace();
264: }
265: return;
266: }
267: EventQueue.invokeLater(new Runnable() {
268: public void run() {
269: synchronized (XTree.this ) {
270: // Add the new nodes to the MBean tree from leaf to root
271:
272: Dn dn = buildDn(mbean);
273: if (dn.size() == 0)
274: return;
275: Token token = dn.getToken(0);
276: DefaultMutableTreeNode node = null;
277: boolean nodeCreated = true;
278:
279: //
280: // Add the node or replace its user object if already added
281: //
282:
283: String hashKey = dn.getHashKey(token);
284: if (nodes.containsKey(hashKey)) {
285: //already in the tree, means it has been created previously
286: //when adding another node
287: node = nodes.get(hashKey);
288: //sets the user object
289: final Object data = createNodeValue(xmbean,
290: token);
291: final String label = data.toString();
292: final XNodeInfo userObject = new XNodeInfo(
293: Type.MBEAN, data, label, mbean
294: .toString());
295: changeNodeValue(node, userObject);
296: nodeCreated = false;
297: } else {
298: //create a new node
299: node = createDnNode(dn, token, xmbean);
300: if (node != null) {
301: nodes.put(hashKey, node);
302: nodeCreated = true;
303: } else {
304: return;
305: }
306: }
307:
308: //
309: // Add (virtual) nodes without user object if necessary
310: //
311:
312: for (int i = 1; i < dn.size(); i++) {
313: DefaultMutableTreeNode currentNode = null;
314: token = dn.getToken(i);
315: hashKey = dn.getHashKey(token);
316: if (nodes.containsKey(hashKey)) {
317: //node already present
318: if (nodeCreated) {
319: //previous node created, link to do
320: currentNode = nodes.get(hashKey);
321: addChildNode(currentNode, node);
322: return;
323: } else {
324: //both nodes already present
325: return;
326: }
327: } else {
328: //creates the node that can be a virtual one
329: if (token.getKeyDn().equals("domain")) {
330: //better match on keyDn that on Dn
331: currentNode = createDomainNode(dn,
332: token);
333: if (currentNode != null) {
334: final DefaultMutableTreeNode root = (DefaultMutableTreeNode) getModel()
335: .getRoot();
336: addChildNode(root, currentNode);
337: }
338: } else {
339: currentNode = createSubDnNode(dn, token);
340: if (currentNode == null) {
341: //skip
342: continue;
343: }
344: }
345: nodes.put(hashKey, currentNode);
346: addChildNode(currentNode, node);
347: nodeCreated = true;
348: }
349: node = currentNode;
350: }
351: }
352: }
353: });
354: }
355:
356: // Call on EDT
357: private synchronized void changeNodeValue(
358: final DefaultMutableTreeNode node, XNodeInfo nodeValue) {
359: if (node instanceof ComparableDefaultMutableTreeNode) {
360: // should it stay at the same place?
361: DefaultMutableTreeNode clone = (DefaultMutableTreeNode) node
362: .clone();
363: clone.setUserObject(nodeValue);
364: if (((ComparableDefaultMutableTreeNode) node)
365: .compareTo(clone) == 0) {
366: // the order in the tree didn't change
367: node.setUserObject(nodeValue);
368: DefaultTreeModel model = (DefaultTreeModel) getModel();
369: model.nodeChanged(node);
370: } else {
371: // delete the node and re-order it in case the
372: // node value modifies the order in the tree
373: DefaultMutableTreeNode parent = (DefaultMutableTreeNode) node
374: .getParent();
375: removeChildNode(node);
376: node.setUserObject(nodeValue);
377: addChildNode(parent, node);
378: }
379: } else {
380: // not comparable stays at the same place
381: node.setUserObject(nodeValue);
382: DefaultTreeModel model = (DefaultTreeModel) getModel();
383: model.nodeChanged(node);
384: }
385: // Load the MBean metadata if type is MBEAN
386: if (nodeValue.getType().equals(Type.MBEAN)) {
387: XMBeanInfo.loadInfo(node);
388: DefaultTreeModel model = (DefaultTreeModel) getModel();
389: model.nodeStructureChanged(node);
390: }
391: // Clear the current selection and set it
392: // again so valueChanged() gets called
393: if (node == getLastSelectedPathComponent()) {
394: TreePath selectionPath = getSelectionPath();
395: clearSelection();
396: setSelectionPath(selectionPath);
397: }
398: }
399:
400: //creates the domain node, called on a domain token
401: private DefaultMutableTreeNode createDomainNode(Dn dn, Token token) {
402: DefaultMutableTreeNode node = new ComparableDefaultMutableTreeNode();
403: String label = dn.getDomain();
404: XNodeInfo userObject = new XNodeInfo(Type.NONMBEAN, label,
405: label, label);
406: node.setUserObject(userObject);
407: return node;
408: }
409:
410: //creates the node corresponding to the whole Dn
411: private DefaultMutableTreeNode createDnNode(Dn dn, Token token,
412: XMBean xmbean) {
413: DefaultMutableTreeNode node = new ComparableDefaultMutableTreeNode();
414: Object data = createNodeValue(xmbean, token);
415: String label = data.toString();
416: XNodeInfo userObject = new XNodeInfo(Type.MBEAN, data, label,
417: xmbean.getObjectName().toString());
418: node.setUserObject(userObject);
419: XMBeanInfo.loadInfo(node);
420: return node;
421: }
422:
423: //creates a node with the token value, call for each non domain sub
424: //dn token
425: private DefaultMutableTreeNode createSubDnNode(Dn dn, Token token) {
426: DefaultMutableTreeNode node = new ComparableDefaultMutableTreeNode();
427: String label = isKeyValueView() ? token.toString() : token
428: .getValue().toString();
429: XNodeInfo userObject = new XNodeInfo(Type.NONMBEAN, label,
430: label, token.toString());
431: node.setUserObject(userObject);
432: return node;
433: }
434:
435: private Object createNodeValue(XMBean xmbean, Token token) {
436: String label = isKeyValueView() ? token.toString() : token
437: .getValue().toString();
438: xmbean.setText(label);
439: return xmbean;
440: }
441:
442: /**
443: * Parses MBean ObjectName comma-separated properties string and put the
444: * individual key/value pairs into the map. Key order in the properties
445: * string is preserved by the map.
446: */
447: private Map<String, String> extractKeyValuePairs(String properties,
448: ObjectName mbean) {
449: String props = properties;
450: Map<String, String> map = new LinkedHashMap<String, String>();
451: int eq = props.indexOf("=");
452: while (eq != -1) {
453: String key = props.substring(0, eq);
454: String value = mbean.getKeyProperty(key);
455: map.put(key, value);
456: props = props.substring(key.length() + 1 + value.length());
457: if (props.startsWith(",")) {
458: props = props.substring(1);
459: }
460: eq = props.indexOf("=");
461: }
462: return map;
463: }
464:
465: /**
466: * Returns the ordered key property list that will be used to build the
467: * MBean tree. If the "com.sun.tools.jconsole.mbeans.keyPropertyList" system
468: * property is not specified, then the ordered key property list used
469: * to build the MBean tree will be the one returned by the method
470: * ObjectName.getKeyPropertyListString() with "type" as first key,
471: * and "j2eeType" as second key, if present. If any of the keys specified
472: * in the comma-separated key property list does not apply to the given
473: * MBean then it will be discarded.
474: */
475: private String getKeyPropertyListString(ObjectName mbean) {
476: String props = mbean.getKeyPropertyListString();
477: Map<String, String> map = extractKeyValuePairs(props, mbean);
478: StringBuilder sb = new StringBuilder();
479: // Add the key/value pairs to the buffer following the
480: // key order defined by the "orderedKeyPropertyList"
481: for (String key : orderedKeyPropertyList) {
482: if (map.containsKey(key)) {
483: sb.append(key + "=" + map.get(key) + ",");
484: map.remove(key);
485: }
486: }
487: // Add the remaining key/value pairs to the buffer
488: for (Map.Entry<String, String> entry : map.entrySet()) {
489: sb.append(entry.getKey() + "=" + entry.getValue() + ",");
490: }
491: String orderedKeyPropertyListString = sb.toString();
492: orderedKeyPropertyListString = orderedKeyPropertyListString
493: .substring(0, orderedKeyPropertyListString.length() - 1);
494: return orderedKeyPropertyListString;
495: }
496:
497: /**
498: * Builds the Dn for the given MBean.
499: */
500: private Dn buildDn(ObjectName mbean) {
501:
502: String domain = mbean.getDomain();
503: String globalDn = getKeyPropertyListString(mbean);
504:
505: Dn dn = buildDn(domain, globalDn, mbean);
506:
507: //update the Dn tokens to add the domain
508: dn.updateDn();
509:
510: //reverse the Dn (from leaf to root)
511: dn.reverseOrder();
512:
513: //compute the hashDn
514: dn.computeHashDn();
515:
516: return dn;
517: }
518:
519: /**
520: * Builds the Dn for the given MBean.
521: */
522: private Dn buildDn(String domain, String globalDn, ObjectName mbean) {
523: Dn dn = new Dn(domain, globalDn);
524: String keyDn = "no_key";
525: if (isTreeView()) {
526: String props = globalDn;
527: Map<String, String> map = extractKeyValuePairs(props, mbean);
528: for (Map.Entry<String, String> entry : map.entrySet()) {
529: dn.addToken(new Token(keyDn, entry.getKey() + "="
530: + entry.getValue()));
531: }
532: } else {
533: //flat view
534: dn.addToken(new Token(keyDn, "properties=" + globalDn));
535: }
536: return dn;
537: }
538:
539: //
540: //utility objects
541: //
542:
543: public static class ComparableDefaultMutableTreeNode extends
544: DefaultMutableTreeNode implements
545: Comparable<DefaultMutableTreeNode> {
546: public int compareTo(DefaultMutableTreeNode node) {
547: return (this .toString().compareTo(node.toString()));
548: }
549: }
550:
551: //
552: //tree preferences
553: //
554:
555: private boolean treeView;
556: private boolean treeViewInit = false;
557:
558: public boolean isTreeView() {
559: if (!treeViewInit) {
560: treeView = getTreeViewValue();
561: treeViewInit = true;
562: }
563: return treeView;
564: }
565:
566: private boolean getTreeViewValue() {
567: String treeView = System.getProperty("treeView");
568: return ((treeView == null) ? true : !(treeView.equals("false")));
569: }
570:
571: //
572: //MBean key-value preferences
573: //
574:
575: private boolean keyValueView = Boolean.getBoolean("keyValueView");
576:
577: public boolean isKeyValueView() {
578: return keyValueView;
579: }
580:
581: //
582: //utility classes
583: //
584:
585: public static class Dn {
586:
587: private String domain;
588: private String dn;
589: private String hashDn;
590: private ArrayList<Token> tokens = new ArrayList<Token>();
591:
592: public Dn(String domain, String dn) {
593: this .domain = domain;
594: this .dn = dn;
595: }
596:
597: public void clearTokens() {
598: tokens.clear();
599: }
600:
601: public void addToken(Token token) {
602: tokens.add(token);
603: }
604:
605: public void addToken(int index, Token token) {
606: tokens.add(index, token);
607: }
608:
609: public void setToken(int index, Token token) {
610: tokens.set(index, token);
611: }
612:
613: public void removeToken(int index) {
614: tokens.remove(index);
615: }
616:
617: public Token getToken(int index) {
618: return tokens.get(index);
619: }
620:
621: public void reverseOrder() {
622: ArrayList<Token> newOrder = new ArrayList<Token>(tokens
623: .size());
624: for (int i = tokens.size() - 1; i >= 0; i--) {
625: newOrder.add(tokens.get(i));
626: }
627: tokens = newOrder;
628: }
629:
630: public int size() {
631: return tokens.size();
632: }
633:
634: public String getDomain() {
635: return domain;
636: }
637:
638: public String getDn() {
639: return dn;
640: }
641:
642: public String getHashDn() {
643: return hashDn;
644: }
645:
646: public String getHashKey(Token token) {
647: final int begin = getHashDn().indexOf(token.getHashToken());
648: return getHashDn().substring(begin, getHashDn().length());
649: }
650:
651: public void computeHashDn() {
652: final StringBuilder hashDn = new StringBuilder();
653: final int tokensSize = tokens.size();
654: for (int i = 0; i < tokensSize; i++) {
655: Token token = tokens.get(i);
656: String hashToken = token.getHashToken();
657: if (hashToken == null) {
658: hashToken = token.getToken() + (tokensSize - i);
659: token.setHashToken(hashToken);
660: }
661: hashDn.append(hashToken);
662: hashDn.append(",");
663: }
664: if (tokensSize > 0) {
665: this .hashDn = hashDn.substring(0, hashDn.length() - 1);
666: } else {
667: this .hashDn = "";
668: }
669: }
670:
671: /**
672: * Adds the domain as the first token in the Dn.
673: */
674: public void updateDn() {
675: addToken(0, new Token("domain", "domain=" + getDomain()));
676: }
677:
678: public String toString() {
679: return tokens.toString();
680: }
681: }
682:
683: public static class Token {
684:
685: private String keyDn;
686: private String token;
687: private String hashToken;
688: private String key;
689: private String value;
690:
691: public Token(String keyDn, String token) {
692: this .keyDn = keyDn;
693: this .token = token;
694: buildKeyValue();
695: }
696:
697: public Token(String keyDn, String token, String hashToken) {
698: this .keyDn = keyDn;
699: this .token = token;
700: this .hashToken = hashToken;
701: buildKeyValue();
702: }
703:
704: public String getKeyDn() {
705: return keyDn;
706: }
707:
708: public String getToken() {
709: return token;
710: }
711:
712: public void setValue(String value) {
713: this .value = value;
714: this .token = key + "=" + value;
715: }
716:
717: public void setKey(String key) {
718: this .key = key;
719: this .token = key + "=" + value;
720: }
721:
722: public void setKeyDn(String keyDn) {
723: this .keyDn = keyDn;
724: }
725:
726: public void setHashToken(String hashToken) {
727: this .hashToken = hashToken;
728: }
729:
730: public String getHashToken() {
731: return hashToken;
732: }
733:
734: public String getKey() {
735: return key;
736: }
737:
738: public String getValue() {
739: return value;
740: }
741:
742: public String toString() {
743: return getToken();
744: }
745:
746: public boolean equals(Object object) {
747: if (object instanceof Token) {
748: return token.equals(((Token) object));
749: } else {
750: return false;
751: }
752: }
753:
754: private void buildKeyValue() {
755: int index = token.indexOf("=");
756: if (index < 0) {
757: key = token;
758: value = token;
759: } else {
760: key = token.substring(0, index);
761: value = token.substring(index + 1, token.length());
762: }
763: }
764: }
765: }
|