| /* |
| * Copyright 2004-2006 Sun Microsystems, Inc. All Rights Reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. Sun designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Sun in the LICENSE file that accompanied this code. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, |
| * CA 95054 USA or visit www.sun.com if you need additional information or |
| * have any questions. |
| */ |
| |
| package sun.tools.jconsole.inspector; |
| |
| import java.awt.EventQueue; |
| import java.util.*; |
| import javax.management.*; |
| import javax.swing.*; |
| import javax.swing.tree.*; |
| import sun.tools.jconsole.JConsole; |
| import sun.tools.jconsole.MBeansTab; |
| import sun.tools.jconsole.Resources; |
| import sun.tools.jconsole.inspector.XNodeInfo; |
| import sun.tools.jconsole.inspector.XNodeInfo.Type; |
| |
| @SuppressWarnings("serial") |
| public class XTree extends JTree { |
| |
| private static final List<String> orderedKeyPropertyList = |
| new ArrayList<String>(); |
| static { |
| String keyPropertyList = |
| System.getProperty("com.sun.tools.jconsole.mbeans.keyPropertyList"); |
| if (keyPropertyList == null) { |
| orderedKeyPropertyList.add("type"); |
| orderedKeyPropertyList.add("j2eeType"); |
| } else { |
| StringTokenizer st = new StringTokenizer(keyPropertyList, ","); |
| while (st.hasMoreTokens()) { |
| orderedKeyPropertyList.add(st.nextToken()); |
| } |
| } |
| } |
| |
| private MBeansTab mbeansTab; |
| |
| private Map<String, DefaultMutableTreeNode> nodes = |
| new HashMap<String, DefaultMutableTreeNode>(); |
| |
| public XTree(MBeansTab mbeansTab) { |
| this(new DefaultMutableTreeNode("MBeanTreeRootNode"), mbeansTab); |
| } |
| |
| public XTree(TreeNode root, MBeansTab mbeansTab) { |
| super(root); |
| this.mbeansTab = mbeansTab; |
| setRootVisible(false); |
| setShowsRootHandles(true); |
| ToolTipManager.sharedInstance().registerComponent(this); |
| } |
| |
| /** |
| * This method removes the node from its parent |
| */ |
| // Call on EDT |
| private synchronized void removeChildNode(DefaultMutableTreeNode child) { |
| DefaultTreeModel model = (DefaultTreeModel) getModel(); |
| model.removeNodeFromParent(child); |
| } |
| |
| /** |
| * This method adds the child to the specified parent node |
| * at specific index. |
| */ |
| // Call on EDT |
| private synchronized void addChildNode( |
| DefaultMutableTreeNode parent, |
| DefaultMutableTreeNode child, |
| int index) { |
| // Tree does not show up when there is only the root node |
| // |
| DefaultTreeModel model = (DefaultTreeModel) getModel(); |
| DefaultMutableTreeNode root = (DefaultMutableTreeNode) model.getRoot(); |
| boolean rootLeaf = root.isLeaf(); |
| model.insertNodeInto(child, parent, index); |
| if (rootLeaf) { |
| model.nodeStructureChanged(root); |
| } |
| } |
| |
| /** |
| * This method adds the child to the specified parent node. |
| * The index where the child is to be added depends on the |
| * child node being Comparable or not. If the child node is |
| * not Comparable then it is added at the end, i.e. right |
| * after the current parent's children. |
| */ |
| // Call on EDT |
| private synchronized void addChildNode( |
| DefaultMutableTreeNode parent, DefaultMutableTreeNode child) { |
| int childCount = parent.getChildCount(); |
| if (childCount == 0) { |
| addChildNode(parent, child, 0); |
| } else if (child instanceof ComparableDefaultMutableTreeNode) { |
| ComparableDefaultMutableTreeNode comparableChild = |
| (ComparableDefaultMutableTreeNode)child; |
| int i = 0; |
| for (; i < childCount; i++) { |
| DefaultMutableTreeNode brother = |
| (DefaultMutableTreeNode) parent.getChildAt(i); |
| //child < brother |
| if (comparableChild.compareTo(brother) < 0) { |
| addChildNode(parent, child, i); |
| break; |
| } |
| //child = brother |
| else if (comparableChild.compareTo(brother) == 0) { |
| addChildNode(parent, child, i); |
| break; |
| } |
| } |
| //child < all brothers |
| if (i == childCount) { |
| addChildNode(parent, child, childCount); |
| } |
| } else { |
| //not comparable, add at the end |
| addChildNode(parent, child, childCount); |
| } |
| } |
| |
| /** |
| * This method removes all the displayed nodes from the tree, |
| * but does not affect actual MBeanServer contents. |
| */ |
| // Call on EDT |
| public synchronized void removeAll() { |
| DefaultTreeModel model = (DefaultTreeModel) getModel(); |
| DefaultMutableTreeNode root = (DefaultMutableTreeNode) model.getRoot(); |
| root.removeAllChildren(); |
| model.nodeStructureChanged(root); |
| nodes.clear(); |
| } |
| |
| public void delMBeanFromView(final ObjectName mbean) { |
| EventQueue.invokeLater(new Runnable() { |
| public void run() { |
| // We assume here that MBeans are removed one by one (on MBean |
| // unregistered notification). Deletes the tree node associated |
| // with the given MBean and recursively all the node parents |
| // which are leaves and non XMBean. |
| // |
| synchronized (XTree.this) { |
| DefaultMutableTreeNode node = null; |
| Dn dn = buildDn(mbean); |
| if (dn.size() > 0) { |
| DefaultTreeModel model = (DefaultTreeModel) getModel(); |
| Token token = dn.getToken(0); |
| String hashKey = dn.getHashKey(token); |
| node = nodes.get(hashKey); |
| if ((node != null) && (!node.isRoot())) { |
| if (hasMBeanChildren(node)) { |
| removeNonMBeanChildren(node); |
| String label = token.getValue().toString(); |
| XNodeInfo userObject = new XNodeInfo( |
| Type.NONMBEAN, label, |
| label, token.toString()); |
| changeNodeValue(node, userObject); |
| } else { |
| DefaultMutableTreeNode parent = |
| (DefaultMutableTreeNode) node.getParent(); |
| model.removeNodeFromParent(node); |
| nodes.remove(hashKey); |
| delParentFromView(dn, 1, parent); |
| } |
| } |
| } |
| } |
| } |
| }); |
| } |
| |
| /** |
| * Returns true if any of the children nodes is an MBean. |
| */ |
| private boolean hasMBeanChildren(DefaultMutableTreeNode node) { |
| for (Enumeration e = node.children(); e.hasMoreElements(); ) { |
| DefaultMutableTreeNode n = (DefaultMutableTreeNode) e.nextElement(); |
| if (((XNodeInfo) n.getUserObject()).getType().equals(Type.MBEAN)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Remove all the children nodes which are not MBean. |
| */ |
| private void removeNonMBeanChildren(DefaultMutableTreeNode node) { |
| Set<DefaultMutableTreeNode> metadataNodes = |
| new HashSet<DefaultMutableTreeNode>(); |
| DefaultTreeModel model = (DefaultTreeModel) getModel(); |
| for (Enumeration e = node.children(); e.hasMoreElements(); ) { |
| DefaultMutableTreeNode n = (DefaultMutableTreeNode) e.nextElement(); |
| if (!((XNodeInfo) n.getUserObject()).getType().equals(Type.MBEAN)) { |
| metadataNodes.add(n); |
| } |
| } |
| for (DefaultMutableTreeNode n : metadataNodes) { |
| model.removeNodeFromParent(n); |
| } |
| } |
| |
| /** |
| * Removes only the parent nodes which are non MBean and leaf. |
| * This method assumes the child nodes have been removed before. |
| */ |
| private DefaultMutableTreeNode delParentFromView( |
| Dn dn, int index, DefaultMutableTreeNode node) { |
| if ((!node.isRoot()) && node.isLeaf() && |
| (!(((XNodeInfo) node.getUserObject()).getType().equals(Type.MBEAN)))) { |
| DefaultMutableTreeNode parent = |
| (DefaultMutableTreeNode) node.getParent(); |
| removeChildNode(node); |
| String hashKey = dn.getHashKey(dn.getToken(index)); |
| nodes.remove(hashKey); |
| delParentFromView(dn, index + 1, parent); |
| } |
| return node; |
| } |
| |
| public synchronized void addMBeanToView(final ObjectName mbean) { |
| final XMBean xmbean; |
| try { |
| xmbean = new XMBean(mbean, mbeansTab); |
| if (xmbean == null) { |
| return; |
| } |
| } catch (Exception e) { |
| // Got exception while trying to retrieve the |
| // given MBean from the underlying MBeanServer |
| // |
| if (JConsole.isDebug()) { |
| e.printStackTrace(); |
| } |
| return; |
| } |
| EventQueue.invokeLater(new Runnable() { |
| public void run() { |
| synchronized (XTree.this) { |
| // Add the new nodes to the MBean tree from leaf to root |
| |
| Dn dn = buildDn(mbean); |
| if (dn.size() == 0) return; |
| Token token = dn.getToken(0); |
| DefaultMutableTreeNode node = null; |
| boolean nodeCreated = true; |
| |
| // |
| // Add the node or replace its user object if already added |
| // |
| |
| String hashKey = dn.getHashKey(token); |
| if (nodes.containsKey(hashKey)) { |
| //already in the tree, means it has been created previously |
| //when adding another node |
| node = nodes.get(hashKey); |
| //sets the user object |
| final Object data = createNodeValue(xmbean, token); |
| final String label = data.toString(); |
| final XNodeInfo userObject = |
| new XNodeInfo(Type.MBEAN, data, label, mbean.toString()); |
| changeNodeValue(node, userObject); |
| nodeCreated = false; |
| } else { |
| //create a new node |
| node = createDnNode(dn, token, xmbean); |
| if (node != null) { |
| nodes.put(hashKey, node); |
| nodeCreated = true; |
| } else { |
| return; |
| } |
| } |
| |
| // |
| // Add (virtual) nodes without user object if necessary |
| // |
| |
| for (int i = 1; i < dn.size(); i++) { |
| DefaultMutableTreeNode currentNode = null; |
| token = dn.getToken(i); |
| hashKey = dn.getHashKey(token); |
| if (nodes.containsKey(hashKey)) { |
| //node already present |
| if (nodeCreated) { |
| //previous node created, link to do |
| currentNode = nodes.get(hashKey); |
| addChildNode(currentNode, node); |
| return; |
| } else { |
| //both nodes already present |
| return; |
| } |
| } else { |
| //creates the node that can be a virtual one |
| if (token.getKeyDn().equals("domain")) { |
| //better match on keyDn that on Dn |
| currentNode = createDomainNode(dn, token); |
| if (currentNode != null) { |
| final DefaultMutableTreeNode root = |
| (DefaultMutableTreeNode) getModel().getRoot(); |
| addChildNode(root, currentNode); |
| } |
| } else { |
| currentNode = createSubDnNode(dn, token); |
| if (currentNode == null) { |
| //skip |
| continue; |
| } |
| } |
| nodes.put(hashKey, currentNode); |
| addChildNode(currentNode, node); |
| nodeCreated = true; |
| } |
| node = currentNode; |
| } |
| } |
| } |
| }); |
| } |
| |
| // Call on EDT |
| private synchronized void changeNodeValue( |
| final DefaultMutableTreeNode node, XNodeInfo nodeValue) { |
| if (node instanceof ComparableDefaultMutableTreeNode) { |
| // should it stay at the same place? |
| DefaultMutableTreeNode clone = |
| (DefaultMutableTreeNode) node.clone(); |
| clone.setUserObject(nodeValue); |
| if (((ComparableDefaultMutableTreeNode) node).compareTo(clone) == 0) { |
| // the order in the tree didn't change |
| node.setUserObject(nodeValue); |
| DefaultTreeModel model = (DefaultTreeModel) getModel(); |
| model.nodeChanged(node); |
| } else { |
| // delete the node and re-order it in case the |
| // node value modifies the order in the tree |
| DefaultMutableTreeNode parent = |
| (DefaultMutableTreeNode) node.getParent(); |
| removeChildNode(node); |
| node.setUserObject(nodeValue); |
| addChildNode(parent, node); |
| } |
| } else { |
| // not comparable stays at the same place |
| node.setUserObject(nodeValue); |
| DefaultTreeModel model = (DefaultTreeModel) getModel(); |
| model.nodeChanged(node); |
| } |
| // Load the MBean metadata if type is MBEAN |
| if (nodeValue.getType().equals(Type.MBEAN)) { |
| XMBeanInfo.loadInfo(node); |
| DefaultTreeModel model = (DefaultTreeModel) getModel(); |
| model.nodeStructureChanged(node); |
| } |
| // Clear the current selection and set it |
| // again so valueChanged() gets called |
| if (node == getLastSelectedPathComponent()) { |
| TreePath selectionPath = getSelectionPath(); |
| clearSelection(); |
| setSelectionPath(selectionPath); |
| } |
| } |
| |
| //creates the domain node, called on a domain token |
| private DefaultMutableTreeNode createDomainNode(Dn dn, Token token) { |
| DefaultMutableTreeNode node = new ComparableDefaultMutableTreeNode(); |
| String label = dn.getDomain(); |
| XNodeInfo userObject = |
| new XNodeInfo(Type.NONMBEAN, label, label, label); |
| node.setUserObject(userObject); |
| return node; |
| } |
| |
| //creates the node corresponding to the whole Dn |
| private DefaultMutableTreeNode createDnNode( |
| Dn dn, Token token, XMBean xmbean) { |
| DefaultMutableTreeNode node = new ComparableDefaultMutableTreeNode(); |
| Object data = createNodeValue(xmbean, token); |
| String label = data.toString(); |
| XNodeInfo userObject = new XNodeInfo(Type.MBEAN, data, label, |
| xmbean.getObjectName().toString()); |
| node.setUserObject(userObject); |
| XMBeanInfo.loadInfo(node); |
| return node; |
| } |
| |
| //creates a node with the token value, call for each non domain sub |
| //dn token |
| private DefaultMutableTreeNode createSubDnNode(Dn dn, Token token) { |
| DefaultMutableTreeNode node = new ComparableDefaultMutableTreeNode(); |
| String label = isKeyValueView() ? token.toString() : |
| token.getValue().toString(); |
| XNodeInfo userObject = |
| new XNodeInfo(Type.NONMBEAN, label, label, token.toString()); |
| node.setUserObject(userObject); |
| return node; |
| } |
| |
| private Object createNodeValue(XMBean xmbean, Token token) { |
| String label = isKeyValueView() ? token.toString() : |
| token.getValue().toString(); |
| xmbean.setText(label); |
| return xmbean; |
| } |
| |
| /** |
| * Parses MBean ObjectName comma-separated properties string and put the |
| * individual key/value pairs into the map. Key order in the properties |
| * string is preserved by the map. |
| */ |
| private Map<String,String> extractKeyValuePairs( |
| String properties, ObjectName mbean) { |
| String props = properties; |
| Map<String,String> map = new LinkedHashMap<String,String>(); |
| int eq = props.indexOf("="); |
| while (eq != -1) { |
| String key = props.substring(0, eq); |
| String value = mbean.getKeyProperty(key); |
| map.put(key, value); |
| props = props.substring(key.length() + 1 + value.length()); |
| if (props.startsWith(",")) { |
| props = props.substring(1); |
| } |
| eq = props.indexOf("="); |
| } |
| return map; |
| } |
| |
| /** |
| * Returns the ordered key property list that will be used to build the |
| * MBean tree. If the "com.sun.tools.jconsole.mbeans.keyPropertyList" system |
| * property is not specified, then the ordered key property list used |
| * to build the MBean tree will be the one returned by the method |
| * ObjectName.getKeyPropertyListString() with "type" as first key, |
| * and "j2eeType" as second key, if present. If any of the keys specified |
| * in the comma-separated key property list does not apply to the given |
| * MBean then it will be discarded. |
| */ |
| private String getKeyPropertyListString(ObjectName mbean) { |
| String props = mbean.getKeyPropertyListString(); |
| Map<String,String> map = extractKeyValuePairs(props, mbean); |
| StringBuilder sb = new StringBuilder(); |
| // Add the key/value pairs to the buffer following the |
| // key order defined by the "orderedKeyPropertyList" |
| for (String key : orderedKeyPropertyList) { |
| if (map.containsKey(key)) { |
| sb.append(key + "=" + map.get(key) + ","); |
| map.remove(key); |
| } |
| } |
| // Add the remaining key/value pairs to the buffer |
| for (Map.Entry<String,String> entry : map.entrySet()) { |
| sb.append(entry.getKey() + "=" + entry.getValue() + ","); |
| } |
| String orderedKeyPropertyListString = sb.toString(); |
| orderedKeyPropertyListString = orderedKeyPropertyListString.substring( |
| 0, orderedKeyPropertyListString.length() - 1); |
| return orderedKeyPropertyListString; |
| } |
| |
| /** |
| * Builds the Dn for the given MBean. |
| */ |
| private Dn buildDn(ObjectName mbean) { |
| |
| String domain = mbean.getDomain(); |
| String globalDn = getKeyPropertyListString(mbean); |
| |
| Dn dn = buildDn(domain, globalDn, mbean); |
| |
| //update the Dn tokens to add the domain |
| dn.updateDn(); |
| |
| //reverse the Dn (from leaf to root) |
| dn.reverseOrder(); |
| |
| //compute the hashDn |
| dn.computeHashDn(); |
| |
| return dn; |
| } |
| |
| /** |
| * Builds the Dn for the given MBean. |
| */ |
| private Dn buildDn(String domain, String globalDn, ObjectName mbean) { |
| Dn dn = new Dn(domain, globalDn); |
| String keyDn = "no_key"; |
| if (isTreeView()) { |
| String props = globalDn; |
| Map<String,String> map = extractKeyValuePairs(props, mbean); |
| for (Map.Entry<String,String> entry : map.entrySet()) { |
| dn.addToken(new Token(keyDn, |
| entry.getKey() + "=" + entry.getValue())); |
| } |
| } else { |
| //flat view |
| dn.addToken(new Token(keyDn, "properties=" + globalDn)); |
| } |
| return dn; |
| } |
| |
| // |
| //utility objects |
| // |
| |
| public static class ComparableDefaultMutableTreeNode |
| extends DefaultMutableTreeNode |
| implements Comparable<DefaultMutableTreeNode> { |
| public int compareTo(DefaultMutableTreeNode node) { |
| return (this.toString().compareTo(node.toString())); |
| } |
| } |
| |
| // |
| //tree preferences |
| // |
| |
| private boolean treeView; |
| private boolean treeViewInit = false; |
| public boolean isTreeView() { |
| if (!treeViewInit) { |
| treeView = getTreeViewValue(); |
| treeViewInit = true; |
| } |
| return treeView; |
| } |
| |
| private boolean getTreeViewValue() { |
| String treeView = System.getProperty("treeView"); |
| return ((treeView == null) ? true : !(treeView.equals("false"))); |
| } |
| |
| // |
| //MBean key-value preferences |
| // |
| |
| private boolean keyValueView = Boolean.getBoolean("keyValueView"); |
| public boolean isKeyValueView() { |
| return keyValueView; |
| } |
| |
| // |
| //utility classes |
| // |
| |
| public static class Dn { |
| |
| private String domain; |
| private String dn; |
| private String hashDn; |
| private ArrayList<Token> tokens = new ArrayList<Token>(); |
| |
| public Dn(String domain, String dn) { |
| this.domain = domain; |
| this.dn = dn; |
| } |
| |
| public void clearTokens() { |
| tokens.clear(); |
| } |
| |
| public void addToken(Token token) { |
| tokens.add(token); |
| } |
| |
| public void addToken(int index, Token token) { |
| tokens.add(index, token); |
| } |
| |
| public void setToken(int index, Token token) { |
| tokens.set(index, token); |
| } |
| |
| public void removeToken(int index) { |
| tokens.remove(index); |
| } |
| |
| public Token getToken(int index) { |
| return tokens.get(index); |
| } |
| |
| public void reverseOrder() { |
| ArrayList<Token> newOrder = new ArrayList<Token>(tokens.size()); |
| for (int i = tokens.size() - 1; i >= 0; i--) { |
| newOrder.add(tokens.get(i)); |
| } |
| tokens = newOrder; |
| } |
| |
| public int size() { |
| return tokens.size(); |
| } |
| |
| public String getDomain() { |
| return domain; |
| } |
| |
| public String getDn() { |
| return dn; |
| } |
| |
| public String getHashDn() { |
| return hashDn; |
| } |
| |
| public String getHashKey(Token token) { |
| final int begin = getHashDn().indexOf(token.getHashToken()); |
| return getHashDn().substring(begin, getHashDn().length()); |
| } |
| |
| public void computeHashDn() { |
| final StringBuilder hashDn = new StringBuilder(); |
| final int tokensSize = tokens.size(); |
| for (int i = 0; i < tokensSize; i++) { |
| Token token = tokens.get(i); |
| String hashToken = token.getHashToken(); |
| if (hashToken == null) { |
| hashToken = token.getToken() + (tokensSize - i); |
| token.setHashToken(hashToken); |
| } |
| hashDn.append(hashToken); |
| hashDn.append(","); |
| } |
| if (tokensSize > 0) { |
| this.hashDn = hashDn.substring(0, hashDn.length() - 1); |
| } else { |
| this.hashDn = ""; |
| } |
| } |
| |
| /** |
| * Adds the domain as the first token in the Dn. |
| */ |
| public void updateDn() { |
| addToken(0, new Token("domain", "domain=" + getDomain())); |
| } |
| |
| public String toString() { |
| return tokens.toString(); |
| } |
| } |
| |
| public static class Token { |
| |
| private String keyDn; |
| private String token; |
| private String hashToken; |
| private String key; |
| private String value; |
| |
| public Token(String keyDn, String token) { |
| this.keyDn = keyDn; |
| this.token = token; |
| buildKeyValue(); |
| } |
| |
| public Token(String keyDn, String token, String hashToken) { |
| this.keyDn = keyDn; |
| this.token = token; |
| this.hashToken = hashToken; |
| buildKeyValue(); |
| } |
| |
| public String getKeyDn() { |
| return keyDn; |
| } |
| |
| public String getToken() { |
| return token; |
| } |
| |
| public void setValue(String value) { |
| this.value = value; |
| this.token = key + "=" + value; |
| } |
| |
| public void setKey(String key) { |
| this.key = key; |
| this.token = key + "=" + value; |
| } |
| |
| public void setKeyDn(String keyDn) { |
| this.keyDn = keyDn; |
| } |
| |
| public void setHashToken(String hashToken) { |
| this.hashToken = hashToken; |
| } |
| |
| public String getHashToken() { |
| return hashToken; |
| } |
| |
| public String getKey() { |
| return key; |
| } |
| |
| public String getValue() { |
| return value; |
| } |
| |
| public String toString(){ |
| return getToken(); |
| } |
| |
| public boolean equals(Object object) { |
| if (object instanceof Token) { |
| return token.equals(((Token) object)); |
| } else { |
| return false; |
| } |
| } |
| |
| private void buildKeyValue() { |
| int index = token.indexOf("="); |
| if (index < 0) { |
| key = token; |
| value = token; |
| } else { |
| key = token.substring(0, index); |
| value = token.substring(index + 1, token.length()); |
| } |
| } |
| } |
| } |