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 }