1 /* 2 * Copyright (c) 2004, 2006, 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.awt.EventQueue; 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.Resources; 36 import sun.tools.jconsole.inspector.XNodeInfo; 37 import 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 static { 45 String keyPropertyList = 46 System.getProperty("com.sun.tools.jconsole.mbeans.keyPropertyList"); 47 if (keyPropertyList == null) { 48 orderedKeyPropertyList.add("type"); 49 orderedKeyPropertyList.add("j2eeType"); 50 } else { 51 StringTokenizer st = new StringTokenizer(keyPropertyList, ","); 52 while (st.hasMoreTokens()) { 53 orderedKeyPropertyList.add(st.nextToken()); 54 } 55 } 56 } 57 58 private MBeansTab mbeansTab; 59 60 private Map<String, DefaultMutableTreeNode> nodes = 61 new HashMap<String, DefaultMutableTreeNode>(); 62 63 public XTree(MBeansTab mbeansTab) { 64 this(new DefaultMutableTreeNode("MBeanTreeRootNode"), mbeansTab); 65 } 66 67 public XTree(TreeNode root, MBeansTab mbeansTab) { 68 super(root); 69 this.mbeansTab = mbeansTab; 70 setRootVisible(false); 71 setShowsRootHandles(true); 72 ToolTipManager.sharedInstance().registerComponent(this); 73 } 74 75 /** 76 * This method removes the node from its parent 77 */ 78 // Call on EDT 79 private synchronized void removeChildNode(DefaultMutableTreeNode child) { 80 DefaultTreeModel model = (DefaultTreeModel) getModel(); 81 model.removeNodeFromParent(child); 82 } 83 84 /** 85 * This method adds the child to the specified parent node 86 * at specific index. 87 */ 88 // Call on EDT 89 private synchronized void addChildNode( 90 DefaultMutableTreeNode parent, 91 DefaultMutableTreeNode child, 92 int index) { 93 // Tree does not show up when there is only the root node 94 // 95 DefaultTreeModel model = (DefaultTreeModel) getModel(); 96 DefaultMutableTreeNode root = (DefaultMutableTreeNode) model.getRoot(); 97 boolean rootLeaf = root.isLeaf(); 98 model.insertNodeInto(child, parent, index); 99 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 = 119 (ComparableDefaultMutableTreeNode)child; 120 int i = 0; 121 for (; i < childCount; i++) { 122 DefaultMutableTreeNode brother = 123 (DefaultMutableTreeNode) parent.getChildAt(i); 124 //child < brother 125 if (comparableChild.compareTo(brother) < 0) { 126 addChildNode(parent, child, i); 127 break; 128 } 129 //child = brother 130 else if (comparableChild.compareTo(brother) == 0) { 131 addChildNode(parent, child, i); 132 break; 133 } 134 } 135 //child < all brothers 136 if (i == childCount) { 137 addChildNode(parent, child, childCount); 138 } 139 } else { 140 //not comparable, add at the end 141 addChildNode(parent, child, childCount); 142 } 143 } 144 145 /** 146 * This method removes all the displayed nodes from the tree, 147 * but does not affect actual MBeanServer contents. 148 */ 149 // Call on EDT 150 public synchronized void removeAll() { 151 DefaultTreeModel model = (DefaultTreeModel) getModel(); 152 DefaultMutableTreeNode root = (DefaultMutableTreeNode) model.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().toString(); 178 XNodeInfo userObject = new XNodeInfo( 179 Type.NONMBEAN, label, 180 label, token.toString()); 181 changeNodeValue(node, userObject); 182 } else { 183 DefaultMutableTreeNode parent = 184 (DefaultMutableTreeNode) node.getParent(); 185 model.removeNodeFromParent(node); 186 nodes.remove(hashKey); 187 delParentFromView(dn, 1, parent); 188 } 189 } 190 } 191 } 192 } 193 }); 194 } 195 196 /** 197 * Returns true if any of the children nodes is an MBean. 198 */ 199 private boolean hasMBeanChildren(DefaultMutableTreeNode node) { 200 for (Enumeration e = node.children(); e.hasMoreElements(); ) { 201 DefaultMutableTreeNode n = (DefaultMutableTreeNode) e.nextElement(); 202 if (((XNodeInfo) n.getUserObject()).getType().equals(Type.MBEAN)) { 203 return true; 204 } 205 } 206 return false; 207 } 208 209 /** 210 * Remove all the children nodes which are not MBean. 211 */ 212 private void removeNonMBeanChildren(DefaultMutableTreeNode node) { 213 Set<DefaultMutableTreeNode> metadataNodes = 214 new HashSet<DefaultMutableTreeNode>(); 215 DefaultTreeModel model = (DefaultTreeModel) getModel(); 216 for (Enumeration e = node.children(); e.hasMoreElements(); ) { 217 DefaultMutableTreeNode n = (DefaultMutableTreeNode) e.nextElement(); 218 if (!((XNodeInfo) n.getUserObject()).getType().equals(Type.MBEAN)) { 219 metadataNodes.add(n); 220 } 221 } 222 for (DefaultMutableTreeNode n : metadataNodes) { 223 model.removeNodeFromParent(n); 224 } 225 } 226 227 /** 228 * Removes only the parent nodes which are non MBean and leaf. 229 * This method assumes the child nodes have been removed before. 230 */ 231 private DefaultMutableTreeNode delParentFromView( 232 Dn dn, int index, DefaultMutableTreeNode node) { 233 if ((!node.isRoot()) && node.isLeaf() && 234 (!(((XNodeInfo) node.getUserObject()).getType().equals(Type.MBEAN)))) { 235 DefaultMutableTreeNode parent = 236 (DefaultMutableTreeNode) node.getParent(); 237 removeChildNode(node); 238 String hashKey = dn.getHashKey(dn.getToken(index)); 239 nodes.remove(hashKey); 240 delParentFromView(dn, index + 1, parent); 241 } 242 return node; 243 } 244 245 public synchronized void addMBeanToView(final ObjectName mbean) { 246 final XMBean xmbean; 247 try { 248 xmbean = new XMBean(mbean, mbeansTab); 249 if (xmbean == null) { 250 return; 251 } 252 } catch (Exception e) { 253 // Got exception while trying to retrieve the 254 // given MBean from the underlying MBeanServer 255 // 256 if (JConsole.isDebug()) { 257 e.printStackTrace(); 258 } 259 return; 260 } 261 EventQueue.invokeLater(new Runnable() { 262 public void run() { 263 synchronized (XTree.this) { 264 // Add the new nodes to the MBean tree from leaf to root 265 266 Dn dn = buildDn(mbean); 267 if (dn.size() == 0) return; 268 Token token = dn.getToken(0); 269 DefaultMutableTreeNode node = null; 270 boolean nodeCreated = true; 271 272 // 273 // Add the node or replace its user object if already added 274 // 275 276 String hashKey = dn.getHashKey(token); 277 if (nodes.containsKey(hashKey)) { 278 //already in the tree, means it has been created previously 279 //when adding another node 280 node = nodes.get(hashKey); 281 //sets the user object 282 final Object data = createNodeValue(xmbean, token); 283 final String label = data.toString(); 284 final XNodeInfo userObject = 285 new XNodeInfo(Type.MBEAN, data, label, mbean.toString()); 286 changeNodeValue(node, userObject); 287 nodeCreated = false; 288 } else { 289 //create a new node 290 node = createDnNode(dn, token, xmbean); 291 if (node != null) { 292 nodes.put(hashKey, node); 293 nodeCreated = true; 294 } else { 295 return; 296 } 297 } 298 299 // 300 // Add (virtual) nodes without user object if necessary 301 // 302 303 for (int i = 1; i < dn.size(); i++) { 304 DefaultMutableTreeNode currentNode = null; 305 token = dn.getToken(i); 306 hashKey = dn.getHashKey(token); 307 if (nodes.containsKey(hashKey)) { 308 //node already present 309 if (nodeCreated) { 310 //previous node created, link to do 311 currentNode = nodes.get(hashKey); 312 addChildNode(currentNode, node); 313 return; 314 } else { 315 //both nodes already present 316 return; 317 } 318 } else { 319 //creates the node that can be a virtual one 320 if (token.getKeyDn().equals("domain")) { 321 //better match on keyDn that on Dn 322 currentNode = createDomainNode(dn, token); 323 if (currentNode != null) { 324 final DefaultMutableTreeNode root = 325 (DefaultMutableTreeNode) getModel().getRoot(); 326 addChildNode(root, currentNode); 327 } 328 } else { 329 currentNode = createSubDnNode(dn, token); 330 if (currentNode == null) { 331 //skip 332 continue; 333 } 334 } 335 nodes.put(hashKey, currentNode); 336 addChildNode(currentNode, node); 337 nodeCreated = true; 338 } 339 node = currentNode; 340 } 341 } 342 } 343 }); 344 } 345 346 // Call on EDT 347 private synchronized void changeNodeValue( 348 final DefaultMutableTreeNode node, XNodeInfo nodeValue) { 349 if (node instanceof ComparableDefaultMutableTreeNode) { 350 // should it stay at the same place? 351 DefaultMutableTreeNode clone = 352 (DefaultMutableTreeNode) node.clone(); 353 clone.setUserObject(nodeValue); 354 if (((ComparableDefaultMutableTreeNode) node).compareTo(clone) == 0) { 355 // the order in the tree didn't change 356 node.setUserObject(nodeValue); 357 DefaultTreeModel model = (DefaultTreeModel) getModel(); 358 model.nodeChanged(node); 359 } else { 360 // delete the node and re-order it in case the 361 // node value modifies the order in the tree 362 DefaultMutableTreeNode parent = 363 (DefaultMutableTreeNode) node.getParent(); 364 removeChildNode(node); 365 node.setUserObject(nodeValue); 366 addChildNode(parent, node); 367 } 368 } else { 369 // not comparable stays at the same place 370 node.setUserObject(nodeValue); 371 DefaultTreeModel model = (DefaultTreeModel) getModel(); 372 model.nodeChanged(node); 373 } 374 // Load the MBean metadata if type is MBEAN 375 if (nodeValue.getType().equals(Type.MBEAN)) { 376 XMBeanInfo.loadInfo(node); 377 DefaultTreeModel model = (DefaultTreeModel) getModel(); 378 model.nodeStructureChanged(node); 379 } 380 // Clear the current selection and set it 381 // again so valueChanged() gets called 382 if (node == getLastSelectedPathComponent()) { 383 TreePath selectionPath = getSelectionPath(); 384 clearSelection(); 385 setSelectionPath(selectionPath); 386 } 387 } 388 389 //creates the domain node, called on a domain token 390 private DefaultMutableTreeNode createDomainNode(Dn dn, Token token) { 391 DefaultMutableTreeNode node = new ComparableDefaultMutableTreeNode(); 392 String label = dn.getDomain(); 393 XNodeInfo userObject = 394 new XNodeInfo(Type.NONMBEAN, label, label, label); 395 node.setUserObject(userObject); 396 return node; 397 } 398 399 //creates the node corresponding to the whole Dn 400 private DefaultMutableTreeNode createDnNode( 401 Dn dn, Token token, XMBean xmbean) { 402 DefaultMutableTreeNode node = new ComparableDefaultMutableTreeNode(); 403 Object data = createNodeValue(xmbean, token); 404 String label = data.toString(); 405 XNodeInfo userObject = new XNodeInfo(Type.MBEAN, data, label, 406 xmbean.getObjectName().toString()); 407 node.setUserObject(userObject); 408 XMBeanInfo.loadInfo(node); 409 return node; 410 } 411 412 //creates a node with the token value, call for each non domain sub 413 //dn token 414 private DefaultMutableTreeNode createSubDnNode(Dn dn, Token token) { 415 DefaultMutableTreeNode node = new ComparableDefaultMutableTreeNode(); 416 String label = isKeyValueView() ? token.toString() : 417 token.getValue().toString(); 418 XNodeInfo userObject = 419 new XNodeInfo(Type.NONMBEAN, label, label, token.toString()); 420 node.setUserObject(userObject); 421 return node; 422 } 423 424 private Object createNodeValue(XMBean xmbean, Token token) { 425 String label = isKeyValueView() ? token.toString() : 426 token.getValue().toString(); 427 xmbean.setText(label); 428 return xmbean; 429 } 430 431 /** 432 * Parses MBean ObjectName comma-separated properties string and put the 433 * individual key/value pairs into the map. Key order in the properties 434 * string is preserved by the map. 435 */ 436 private Map<String,String> extractKeyValuePairs( 437 String properties, ObjectName mbean) { 438 String props = properties; 439 Map<String,String> map = new LinkedHashMap<String,String>(); 440 int eq = props.indexOf("="); 441 while (eq != -1) { 442 String key = props.substring(0, eq); 443 String value = mbean.getKeyProperty(key); 444 map.put(key, value); 445 props = props.substring(key.length() + 1 + value.length()); 446 if (props.startsWith(",")) { 447 props = props.substring(1); 448 } 449 eq = props.indexOf("="); 450 } 451 return map; 452 } 453 454 /** 455 * Returns the ordered key property list that will be used to build the 456 * MBean tree. If the "com.sun.tools.jconsole.mbeans.keyPropertyList" system 457 * property is not specified, then the ordered key property list used 458 * to build the MBean tree will be the one returned by the method 459 * ObjectName.getKeyPropertyListString() with "type" as first key, 460 * and "j2eeType" as second key, if present. If any of the keys specified 461 * in the comma-separated key property list does not apply to the given 462 * MBean then it will be discarded. 463 */ 464 private String getKeyPropertyListString(ObjectName mbean) { 465 String props = mbean.getKeyPropertyListString(); 466 Map<String,String> map = extractKeyValuePairs(props, mbean); 467 StringBuilder sb = new StringBuilder(); 468 // Add the key/value pairs to the buffer following the 469 // key order defined by the "orderedKeyPropertyList" 470 for (String key : orderedKeyPropertyList) { 471 if (map.containsKey(key)) { 472 sb.append(key + "=" + map.get(key) + ","); 473 map.remove(key); 474 } 475 } 476 // Add the remaining key/value pairs to the buffer 477 for (Map.Entry<String,String> entry : map.entrySet()) { 478 sb.append(entry.getKey() + "=" + entry.getValue() + ","); 479 } 480 String orderedKeyPropertyListString = sb.toString(); 481 orderedKeyPropertyListString = orderedKeyPropertyListString.substring( 482 0, orderedKeyPropertyListString.length() - 1); 483 return orderedKeyPropertyListString; 484 } 485 486 /** 487 * Builds the Dn for the given MBean. 488 */ 489 private Dn buildDn(ObjectName mbean) { 490 491 String domain = mbean.getDomain(); 492 String globalDn = getKeyPropertyListString(mbean); 493 494 Dn dn = buildDn(domain, globalDn, mbean); 495 496 //update the Dn tokens to add the domain 497 dn.updateDn(); 498 499 //reverse the Dn (from leaf to root) 500 dn.reverseOrder(); 501 502 //compute the hashDn 503 dn.computeHashDn(); 504 505 return dn; 506 } 507 508 /** 509 * Builds the Dn for the given MBean. 510 */ 511 private Dn buildDn(String domain, String globalDn, ObjectName mbean) { 512 Dn dn = new Dn(domain, globalDn); 513 String keyDn = "no_key"; 514 if (isTreeView()) { 515 String props = globalDn; 516 Map<String,String> map = extractKeyValuePairs(props, mbean); 517 for (Map.Entry<String,String> entry : map.entrySet()) { 518 dn.addToken(new Token(keyDn, 519 entry.getKey() + "=" + entry.getValue())); 520 } 521 } else { 522 //flat view 523 dn.addToken(new Token(keyDn, "properties=" + globalDn)); 524 } 525 return dn; 526 } 527 528 // 529 //utility objects 530 // 531 532 public static class ComparableDefaultMutableTreeNode 533 extends DefaultMutableTreeNode 534 implements Comparable<DefaultMutableTreeNode> { 535 public int compareTo(DefaultMutableTreeNode node) { 536 return (this.toString().compareTo(node.toString())); 537 } 538 } 539 540 // 541 //tree preferences 542 // 543 544 private boolean treeView; 545 private boolean treeViewInit = false; 546 public boolean isTreeView() { 547 if (!treeViewInit) { 548 treeView = getTreeViewValue(); 549 treeViewInit = true; 550 } 551 return treeView; 552 } 553 554 private boolean getTreeViewValue() { 555 String treeView = System.getProperty("treeView"); 556 return ((treeView == null) ? true : !(treeView.equals("false"))); 557 } 558 559 // 560 //MBean key-value preferences 561 // 562 563 private boolean keyValueView = Boolean.getBoolean("keyValueView"); 564 public boolean isKeyValueView() { 565 return keyValueView; 566 } 567 568 // 569 //utility classes 570 // 571 572 public static class Dn { 573 574 private String domain; 575 private String dn; 576 private String hashDn; 577 private ArrayList<Token> tokens = new ArrayList<Token>(); 578 579 public Dn(String domain, String dn) { 580 this.domain = domain; 581 this.dn = dn; 582 } 583 584 public void clearTokens() { 585 tokens.clear(); 586 } 587 588 public void addToken(Token token) { 589 tokens.add(token); 590 } 591 592 public void addToken(int index, Token token) { 593 tokens.add(index, token); 594 } 595 596 public void setToken(int index, Token token) { 597 tokens.set(index, token); 598 } 599 600 public void removeToken(int index) { 601 tokens.remove(index); 602 } 603 604 public Token getToken(int index) { 605 return tokens.get(index); 606 } 607 608 public void reverseOrder() { 609 ArrayList<Token> newOrder = new ArrayList<Token>(tokens.size()); 610 for (int i = tokens.size() - 1; i >= 0; i--) { 611 newOrder.add(tokens.get(i)); 612 } 613 tokens = newOrder; 614 } 615 616 public int size() { 617 return tokens.size(); 618 } 619 620 public String getDomain() { 621 return domain; 622 } 623 624 public String getDn() { 625 return dn; 626 } 627 628 public String getHashDn() { 629 return hashDn; 630 } 631 632 public String getHashKey(Token token) { 633 final int begin = getHashDn().indexOf(token.getHashToken()); 634 return getHashDn().substring(begin, getHashDn().length()); 635 } 636 637 public void computeHashDn() { 638 final StringBuilder hashDn = new StringBuilder(); 639 final int tokensSize = tokens.size(); 640 for (int i = 0; i < tokensSize; i++) { 641 Token token = tokens.get(i); 642 String hashToken = token.getHashToken(); 643 if (hashToken == null) { 644 hashToken = token.getToken() + (tokensSize - i); 645 token.setHashToken(hashToken); 646 } 647 hashDn.append(hashToken); 648 hashDn.append(","); 649 } 650 if (tokensSize > 0) { 651 this.hashDn = hashDn.substring(0, hashDn.length() - 1); 652 } else { 653 this.hashDn = ""; 654 } 655 } 656 657 /** 658 * Adds the domain as the first token in the Dn. 659 */ 660 public void updateDn() { 661 addToken(0, new Token("domain", "domain=" + getDomain())); 662 } 663 664 public String toString() { 665 return tokens.toString(); 666 } 667 } 668 669 public static class Token { 670 671 private String keyDn; 672 private String token; 673 private String hashToken; 674 private String key; 675 private String value; 676 677 public Token(String keyDn, String token) { 678 this.keyDn = keyDn; 679 this.token = token; 680 buildKeyValue(); 681 } 682 683 public Token(String keyDn, String token, String hashToken) { 684 this.keyDn = keyDn; 685 this.token = token; 686 this.hashToken = hashToken; 687 buildKeyValue(); 688 } 689 690 public String getKeyDn() { 691 return keyDn; 692 } 693 694 public String getToken() { 695 return token; 696 } 697 698 public void setValue(String value) { 699 this.value = value; 700 this.token = key + "=" + value; 701 } 702 703 public void setKey(String key) { 704 this.key = key; 705 this.token = key + "=" + value; 706 } 707 708 public void setKeyDn(String keyDn) { 709 this.keyDn = keyDn; 710 } 711 712 public void setHashToken(String hashToken) { 713 this.hashToken = hashToken; 714 } 715 716 public String getHashToken() { 717 return hashToken; 718 } 719 720 public String getKey() { 721 return key; 722 } 723 724 public String getValue() { 725 return value; 726 } 727 728 public String toString(){ 729 return getToken(); 730 } 731 732 public boolean equals(Object object) { 733 if (object instanceof Token) { 734 return token.equals(((Token) object)); 735 } else { 736 return false; 737 } 738 } 739 740 private void buildKeyValue() { 741 int index = token.indexOf("="); 742 if (index < 0) { 743 key = token; 744 value = token; 745 } else { 746 key = token.substring(0, index); 747 value = token.substring(index + 1, token.length()); 748 } 749 } 750 } 751 }