package jmxclcl;

import java.util.*;
import java.io.*;
import java.lang.management.*;

import javax.management.ObjectName;
import javax.management.MBeanInfo;
import javax.management.MalformedObjectNameException;
import javax.management.ReflectionException;
import javax.management.MBeanServerConnection;
import javax.management.InstanceNotFoundException;
import javax.management.MBeanAttributeInfo;
import javax.management.MBeanOperationInfo;
import javax.management.MBeanParameterInfo;
import javax.management.Attribute;
import javax.management.AttributeNotFoundException;

import java.text.*;

import common.RMISSLClientSocketFactory;
import common.RMISSLServerSocketFactory;
 
/** 
 * Command-line JMX Console. This class interacts with MBeanServer
 * using JMX protocol. 
*/
public class CLJconsole {
  final static long SECOND = 1000;
  final static long MINUTE = 60 * SECOND;
  final static long HOUR   = 60 * MINUTE;
  final static long DAY    = 24 * HOUR;
  final static DateFormat timeDF = new SimpleDateFormat("HH:mm");
  final static long MEM_POOL_MAX_SZ_KB = 4*1024*1024; // some pools are up to 4 GB.
  private final static DateFormat timeWithSecondsDF = new SimpleDateFormat("HH:mm:ss");
  private final static DateFormat dateDF            = new SimpleDateFormat("yyyy-MM-dd");

  static String passKwd = "";
  static String failKwd = "";
  static boolean verbose = false;
  static Stack beanInfoList = new Stack();
  static Stack beanGettersList = new Stack();
  static Stack beanSettersList = new Stack();
  static Stack beanOperationsList = new Stack();
  static String needSSL;

  static String formatTime(long t) {
    String str;
    if (t < 1 * MINUTE) {
      String seconds = String.format("%.3f", t / (double)SECOND);
      str = Resources.getText("DurationSeconds", seconds);
    } else {
      long remaining = t;
      long days = remaining / DAY;
      remaining %= 1 * DAY;
      long hours = remaining / HOUR;
      remaining %= 1 * HOUR;
      long minutes = remaining / MINUTE;

      if (t >= 1 * DAY) {
        str = Resources.getText("DurationDaysHoursMinutes",
                                days, hours, minutes);
      } else if (t >= 1 * HOUR) {
        str = Resources.getText("DurationHoursMinutes",
                                hours, minutes);
      } else {
        str = Resources.getText("DurationMinutes", minutes);
      }
    }
    return str;
  }

  static String formatNanoTime(long t) {
    long ms = t / 1000000;
    return formatTime(ms);
  }

  static String formatClockTime(long time) {
    return timeDF.format(time);
  }

  static String formatDate(long time) {
    return dateDF.format(time);
  }

  static String formatDateTime(long time) {
    return dateDF.format(time) + " " + timeWithSecondsDF.format(time);
  }

  static String validateStr (String str, boolean res) { 
    return str + (res ? " " + passKwd : " " + failKwd);
  }

  static String validateLong (String value, long from, long to) {
    long valueLong;
    try {
      valueLong = Long.parseLong(value);
    } catch (Exception e) {
      return validateStr(value, false);
    }
    return validateLong(valueLong, from, to);
  }

  static String validateShort (String value, short from, short to) {
    Short valueShort;
    try {
      valueShort = Short.parseShort(value);
    } catch (Exception e) {
      return validateStr(value, false);
    }
    return validateStr(value, valueShort >= from && valueShort <= to);
  }

  static String validateInt (String value, int from, int to) {
    int valueInt;
    try {
      valueInt = Integer.parseInt(value);
    } catch (Exception e) {
      return validateStr(value, false);
    }
    return validateStr(value, valueInt >= from && valueInt <= to);
  }

  static String validateLong (long value, long from, long to) {
    String str = String.format("%,d",value);
    return validateStr(str, value >= from && value <= to);
  }

  static String formatKB (long bytes, long from, long to) {
    if (bytes == -1) {
      return "-1" + Resources.getText(" kbytes");
    }

    long kb = bytes / 1024;
    return validateStr(String.format("%,d",kb) + Resources.getText(" kbytes"),
                      kb >= from && kb <= to);
  }

  static boolean USE_getAllThreadIds = false;
  static int indent = 0;
  static String[] indents_space = 
  {"", 
   "  ", 
   "    ", 
   "      ",
   "        ", 
   "          ",
   "           "};

  static String[] indents = 
  {"", 
   ". ", 
   ". . ", 
   ". . . ",
   ". . . . ", 
   ". . . . . ",
   ". . . . .  "};

  static String FIELD_BEG = ""; // "[";
  static String FIELD_END = ""; // "]";
  static String FIELD_GET = ""; // "Get ";
  static String FIELD_SET = ""; // "Set ";

  static String indent () {
    int ind = indent;
    if (ind < 0) ind = 0;
    return indents[ind % (indents.length - 1)];
  }

  static void out (String msg) {
    System.out.print(msg);
    System.out.flush();
  }
  static void outln (String msg) {
    System.out.println(msg);
    System.out.flush();
  }

  static boolean DEBUG = false;
  public boolean isDebug () { return DEBUG; }

  static void addRow (String label) {
    String msg = indent() + label + "\n";
    out(msg);
  }

  static void out (Throwable throwable) {
    if (DEBUG)
      throwable.printStackTrace(System.out);    
    else
      addRow("[stacktrace: use -debug on command line]");
  }

  static void addRow (String label, String value) {
    String msg = indent() + 
      FIELD_BEG + label + FIELD_END + " : " + FIELD_BEG + value + FIELD_END + "\n";
    out(msg);
  }

  static void addRow (String label1, String value1,
                      String label2, String value2) 
  {
    String msg = indent() +
      FIELD_BEG + label1 + FIELD_END + " : " + FIELD_BEG + value1 + FIELD_END + " " +
      FIELD_BEG + label2 + FIELD_END + " : " + FIELD_BEG + value2 + FIELD_END + "\n";
    out(msg);
  }

  static void addRow (String label1, String value1,
                      String label2, String value2,
                      String label3, String value3) 
  {
    String msg = indent() +
      FIELD_BEG + label1 + FIELD_END + " : " + FIELD_BEG + value1 + FIELD_END + " " +
      FIELD_BEG + label2 + FIELD_END + " : " + FIELD_BEG + value2 + FIELD_END + " " +
      FIELD_BEG + label3 + FIELD_END + " : " + FIELD_BEG + value3 + FIELD_END + "\n";
    out(msg);
  }

  static void addRow (String label1, String value1,
                      String label2, String value2,
                      String label3, String value3, 
                      String label4, String value4) 
  {
    String msg = indent() +
      FIELD_BEG + label1 + FIELD_END + " : " + FIELD_BEG + value1 + FIELD_END + " " +
      FIELD_BEG + label2 + FIELD_END + " : " + FIELD_BEG + value2 + FIELD_END + " " +
      FIELD_BEG + label3 + FIELD_END + " : " + FIELD_BEG + value3 + FIELD_END + " " +
      FIELD_BEG + label4 + FIELD_END + " : " + FIELD_BEG + value4 + FIELD_END + "\n";
    out(msg);
  }

  static String[] argv;
  static String host = null;
  static int port = 9999; 
  static int loopCount  = 1;
  static int loopInterval  = 0;
  static int handshakeTimeout  = 10000;

  static String delayCondObjName;
  static String delayCondAttrName;
  static String delayCondOpName;
  static String delayCondValue;
  static boolean reportForKnownBeans = true;
  static boolean measureAverageRoundtripTime = true;
  /**
   * 
   *  */
  public static void main (String args[]) {
    argv = args;
    parseOptionsFlags();

    if (needSSL != null) {
      boolean needOrapki  = needSSL.toLowerCase().indexOf("orapki") > -1;
      if (needOrapki) {
        // install and init Oracle PKI Provider before any of the factories is initilalized
        oracle.security.pki.OraclePKIProvider opkip = 
          new oracle.security.pki.OraclePKIProvider();
        if (DEBUG) outln(" Oracle PKI: " + opkip);
        java.security.Security.insertProviderAt(opkip, 3);
        
        if(DEBUG) {
          java.security.Provider[] pa = java.security.Security.getProviders();
          for (int p = 0; p < pa.length; p++)
            outln("at " + p + " provider " + pa[p]);
        }
      }

      //TestParamsRMISSLClient.main(new String[]{"ssl_prog", "2000", "1"});
      boolean isDefaultKM  = needSSL.toLowerCase().indexOf("default") > -1;
      try {
        RMISSLClientSocketFactory csf = new RMISSLClientSocketFactory(isDefaultKM);
        csf.initFactory();
      } catch (Exception e) {
        addRow(failKwd + " Got exception " + e);
        out(e);
      }

    }

    while (loopCount-- > 0) {
      try {
        if (verbose) outln(" *** about to obtain stats *** ");
        monitor(argv[0], port);
        if (verbose) outln(" *** stats obtained, " + loopCount + " times remaim ***");
        if (loopCount > 0) // do not wait before exiting
          Thread.sleep(loopInterval);
      } catch (Exception e) {}
    }
  }

  static void parseOptionsFlags () {
    if (argv.length < 1) {
      usage(failKwd + " Command line has too few arguments");
      System.exit(1);
    } 

    for (int i = 0; i < argv.length; i++) {
      if (argv[i].startsWith("-")) {
        if (argv[i].equals("-port") || argv[i].equals("-p")) {
          try {
            port = Integer.parseInt(argv[++i]);
          } catch (Exception e) {
            usage(failKwd + " error: port flag value is not an integer");
            System.exit(1);
          }
        } else if (argv[i].equals("-repeatTimes") || argv[i].equals("-r")) {
          try {
            loopCount = Integer.parseInt(argv[++i]);
          } catch (Exception e) {
            usage(failKwd + " error: repetition count flag value is not an integer");
            System.exit(1);
          }
        } else if (argv[i].equals("-interval") || argv[i].equals("-i")) {
          try {
            loopInterval = Integer.parseInt(argv[++i]);
          } catch (Exception e) {
            usage(failKwd + " error: repetition interval flag value is not an integer");
            System.exit(1);
          }
        } else if (argv[i].equals("-handshakeTimeout") || argv[i].equals("-ht")) {
          try {
            handshakeTimeout = 1000 * Integer.parseInt(argv[++i]);
          } catch (Exception e) {
            usage(failKwd + " error: connection wait flag value is not an integer");
            System.exit(1);
          }
        } else if (argv[i].equals("-delayCond") || argv[i].equals("-dc")) {
          try {
            delayCondObjName = argv[++i];
            delayCondAttrName = argv[++i];
            delayCondOpName = argv[++i];
            delayCondValue = argv[++i];
          } catch (Exception e) {
            usage(failKwd + " error: delayCond arguments incorrect");
            System.exit(1);
          }
        } else if (argv[i].equals("-successKeyword") || argv[i].equals("-s")) {
          passKwd = argv[++i];
        } else if (argv[i].equals("-debug") || argv[i].equals("-d")) {
          DEBUG = true;
          RMISSLServerSocketFactory.TRACE = true;
          RMISSLClientSocketFactory.TRACE = true;
        } else if (argv[i].equals("-failKeyword") || argv[i].equals("-f")) {
          failKwd = argv[++i];
        } else if (argv[i].equals("-begMarker") || argv[i].equals("-b")) {
          FIELD_BEG = argv[++i];
        } else if (argv[i].equals("-endMarker") || argv[i].equals("-e")) {
          FIELD_END = argv[++i];
        } else if (argv[i].equals("-getMarker")) {
          FIELD_GET = argv[++i];
        } else if (argv[i].equals("-setMarker")) {
          FIELD_SET = argv[++i];
        } else if (argv[i].equals("-ssl")) {
          needSSL = argv[++i];
        } else if (argv[i].equals("-verbose") || argv[i].equals("-v")) {
          verbose = true;
        } else if (argv[i].equals("-beanInfo")) {
          beanInfoList.push(argv[++i]);
        } else if (argv[i].equals("-getAttribute")) {
          beanGettersList.push(argv[++i]); // bean
          beanGettersList.push(argv[++i]); // attribute
        } else if (argv[i].equals("-setAttribute")) {
          beanSettersList.push(argv[++i]); // bean
          beanSettersList.push(argv[++i]); // attribute
          beanSettersList.push(argv[++i]); // value
        } else if (argv[i].equals("-invoke")) {
          beanOperationsList.push(argv[++i]); // bean
          String op = argv[++i];
          if (op.indexOf("(") > -1 && op.indexOf(")") > -1) {
            beanOperationsList.push(op.substring(0, op.indexOf("("))); // operation
            String sign = op.substring(op.indexOf("(")+1, op.indexOf(")")).toLowerCase();
            int arity = sign.length();
            String[] types = new String[arity];
            Object[] args = new Object[arity];
            for (int si = 0; si < arity; si++) {
              switch (sign.charAt(si)) {
              case 'i': 
                types[si] = "int";
                args[si] = Integer.parseInt(argv[++i]);
                break;
              case 'l': 
                types[si] = "long";
                args[si] = Long.parseLong(argv[++i]);
                break;
              case 's': 
                types[si] = "short";
                args[si] = Short.parseShort(argv[++i]);
                break;
              case 'f': 
                types[si] = "float";
                args[si] = Float.parseFloat(argv[++i]);
                break;
              case 'd': 
                types[si] = "double";
                args[si] = Double.parseDouble(argv[++i]);
                break;
              case 'b': 
                types[si] = "boolean";
                args[si] = Boolean.parseBoolean(argv[++i]);
                break;
              case 't': 
              default: 
                types[si] = "java.lang.String";
                args[si] = argv[++i];
                break;
              }
            }
            beanOperationsList.push(types);
            beanOperationsList.push(args);
          }
          else {
            beanOperationsList.push(op);
            beanOperationsList.push(new String[]{});
            beanOperationsList.push(new Object[]{});
          }

        } else if (argv[i].equals("-noReport")) {
          reportForKnownBeans = false;
        } else if (argv[i].equals("-noRoundtrip")) {
          measureAverageRoundtripTime  = false;
        } else {
          host = argv[i];
        }
      }
    }

    if (port <= 0) {
      usage(failKwd + " error: port must be positive integer");
      System.exit(1);
    }

    if (loopCount < 0) {
      usage(failKwd + " error: repetition count must be non-negative integer");
      System.exit(1);
    }
    if (loopInterval < 0) {
      usage(failKwd + " error: repetition interval must be non-negative integer");
      System.exit(1);
    }
  }

  static void usageFlags (String err) {
    if (err != null) 
      outln(err);
    outln("Usage: ");
    outln("  <this program> [options...] host  [options...] ");
    outln("  where");
    outln("    host is machine name or IP");
    outln("  options are as follows:");
    outln("    -port number");
    outln("    -p number");
    outln("         port number. Default is 9999");
    outln("    -repeatTimes number");
    outln("    -r number");
    outln("         number of repetitions the client communicates");
    outln("         with the server. Default is 1.");
    outln("    -interval number");
    outln("    -i number");
    outln("         delay between repetitions, ms. Default is 0.");
    outln("    -beanInfo <beanName>");
    outln("         show info of bean <beanName>, thsi includes list of ");
    outln("         attributes and operations with detailed property values of each");
    outln("    -getAttribute  <beanName> attr");
    outln("         show attribute attr of bean <beanName>");
    outln("    Note: <beanName> is either an identifier or a well-formed JMX name");
    outln("          such as \"java.lang:type=MemoryPool,name=Old Generation\".");
    outln("          when <beanName> is an identifier N, it is converted to java.lang:type=N ");
    outln("    -setAttribute <beanName> attr val");
    outln("         set attribute attr of bean <beanName> to value val");
    outln("    -invoke <beanName> <oper> ");
    outln("         invoke operation <oper> of bean <beanName>");
    outln("         <oper> is an identifier or an identifier followed by argument type");
    outln("         descriptors followed by argument values.");
    outln("    -successKeyword string");
    outln("    -s string");
    outln("         marker signalling success of an mBean operation. Default is empty.");
    outln("    -failKeyword string");
    outln("    -f string");
    outln("         marker signalling failure of an mBean operation. Default is empty.");
    outln("    -begMarker string");
    outln("    -b string");
    outln("         marker starting attribute-value pair. Default is empty.");
    outln("    -endMarker string");
    outln("    -e string");
    outln("         marker ending attribute-value pair. Default is empty.");
    outln("    -getMarker string");
    outln("         marker for bean get operation. Default is empty.");
    outln("    -setMarker string");
    outln("         marker for bean attribute set operation. Default is empty.");
    outln("    -handshakeTimeout howLong");
    outln("    -ht howLong");
    outln("         at handshake, retrying connecting for howLong secs every 500ms");
    outln("            default is -ht 10");

    outln("    -delayCond beanName attribute operation value");
    outln("    -dc beanName attribute operation value");
    outln("         after connecting, delay monitoring until a test condition is met");
    
    outln("    -handshakeTimeout integer");
    outln("    -ht howLong");
    outln("         at handshake, retrying connecting for howLong secs every 500ms");
    outln("    -handshakeTimeout integer");
    outln("    -ht howLong");
    outln("         at handshake, retrying connecting for howLong secs every 500ms");
    outln("    -noReport");
    outln("         supress report of properties of all known system beans");
    outln("    -noRoundtrip");
    outln("         do not measure and report client-server roundtrip statistics");

    outln("Examples:");
    outln("  java jmxclcl.CLJconsole foo.bar.com");
    outln("  java jmxclcl.CLJconsole foo.bar.com -p 7777 ");
    outln("  java jmxclcl.CLJconsole foo.bar.com -p 7777 -r 10 -i 30000 ");
    outln("  java jmxclcl.CLJconsole foo.bar.com -r 10 -i 30000 -s PASS -f FAIL ");
    outln("  java jmxclcl.CLJconsole foo.bar.com -beanInfo Runtime");
    outln("  java jmxclcl.CLJconsole foo.bar.com -beanInfo \'java.lang:type=MemoryPool,name=Old Generation\'");
    outln("  java jmxclcl.CLJconsole foo.bar.com -getAttribute Memory Verbose");
    outln("  java jmxclcl.CLJconsole foo.bar.com -setAttribute Memory Verbose true");
    outln("  java jmxclcl.CLJconsole foo.bar.com -invoke Memory gc");
    outln("  java jmxclcl.CLJconsole foo.bar.com -invoke Threading 'getThreadInfo(l)' 4 ");
  }

  static void usage (String err) { usageFlags(err); }
  static void usage () { usage(null); }

  static ObjectName toObjectName (String beanName) {
    if (beanName.indexOf(":") < 0) { // assume simple name was given
      beanName = "java.lang:type=" + beanName;
    }
    ObjectName name = null;
    try {
      name = new ObjectName(beanName);
      addRow(FIELD_GET + " Bean name", beanName + " " + passKwd);
    } catch (MalformedObjectNameException e) {
      addRow(FIELD_GET + " Bean name", beanName + " " + failKwd);
    }
    return name;
  }

  static void showAttributeInfo (MBeanAttributeInfo attr) {
    String name = attr.getName();
    addRow(name);
    indent++;
    addRow("type", attr.getType());
    addRow("descr", attr.getDescription());
    addRow("readable", ""+attr.isReadable());
    addRow("writeable", ""+attr.isWritable());
    addRow("isIs", ""+attr.isIs());
    indent--;
  }

  static void showOperationInfo (MBeanOperationInfo op) {
    String name = op.getName();
    addRow(name);
    indent++;
    {
      MBeanParameterInfo[] sigarr = op.getSignature();
      String sig = "";
      if (sigarr != null) {
        for (int i = 0; i < sigarr.length; i++) {
          if (i != 0) sig += ",";
          sig += sigarr[i].getType();          
        }
        addRow("signature", "(" + sig + ")");
      }
    }
    addRow("type", op.getReturnType());
    addRow("descr", op.getDescription());
    addRow("impact", ""+op.getImpact());
    indent--;
  }

  static Object coerceAttributeLiteralTo (String literal, Object currentVal) {
    if (currentVal == null) return literal;
    Class cl  =  currentVal.getClass();
    try {
      if (cl == Long.class) return Long.parseLong(literal);
      if (cl == Integer.class) return Integer.parseInt(literal);
      if (cl == Boolean.class) return Boolean.parseBoolean(literal);
      if (cl == Byte.class) return Byte.parseByte(literal);
      if (cl == Short.class) return Short.parseShort(literal);
      if (cl == Float.class) return Float.parseFloat(literal);
      if (cl == Double.class) return Double.parseDouble(literal);
    } catch (Exception e) {}
    return literal;
  }

  static void monitor (String host, int port) {
    indent = 0;

    try {

      addRow("JMX RMI Connection...");
      indent++;

      ProxyClient proxyClient = null;

      long timeStamp = System.currentTimeMillis();
      while (true) {
        try {
          proxyClient = ProxyClient.getProxyClient(host, port, "", "");
          if (proxyClient.isDead()) {
            //outln("-- proxyClient isDead ");
            addRow(FIELD_GET + "Connection Proxy", "" + proxyClient + " is dead. " + failKwd);
            return;
          }
          addRow(FIELD_GET + "Connection Proxy", 
                 proxyClient.toString() + " " + passKwd);
          if (delayCondObjName != null && delayCondAttrName != null && delayCondOpName != null && delayCondValue != null) {
            addRow("Check startup condition " + delayCondObjName + " " + delayCondAttrName + " " + delayCondOpName + " " + delayCondValue + " :");
            while (true) {
              ObjectName name  = toObjectName(delayCondObjName);
              Object val = null;
              Object[] params = {};
              String[] paramSign = {};
              try {
                MBeanServerConnection server = proxyClient.getMBeanServerConnection();
                val = server.getAttribute(name, delayCondAttrName);
                if (delayCondOpName.equals("eq")) {
                  if (delayCondValue.equals(""+val))
                    break; // while(true);
                  else
                    continue; // while(true);
                } else if (delayCondOpName.equals("ne")) {
                  if (delayCondValue.equals(""+val))
                    continue; // while(true);
                  else
                    break; // while(true);
                } 
              } catch (Throwable e) {
                if (System.currentTimeMillis() < timeStamp + handshakeTimeout) {
                  System.out.println("  Condition is false (" + delayCondObjName + " " + delayCondAttrName + " =  "  + val  +  " ). Wait..." );
                  Thread.sleep(500);
                  continue; // while(true)
                }
                else {
                  addRow(FIELD_GET + "Startup condition ", delayCondObjName + " " + delayCondAttrName + " " + delayCondOpName + " " + delayCondValue + " " + failKwd);
                  return;
                }
              }
            }
          }
        } catch (Throwable thr) { 
          if (System.currentTimeMillis() < timeStamp + handshakeTimeout) {
            System.out.println("Wait for connection...");
            Thread.sleep(500);
            continue; // while(true)
          }
          else {
            addRow(FIELD_GET + "Connection Proxy", "" + proxyClient + " " + failKwd);
            out(thr);
            return;
          }
        }
        break; // while(true)
      }



      indent--;

      if (reportForKnownBeans) {

        addRow("Beans");
        indent++;

        RuntimeMXBean rmBean = null;

        try {
          rmBean = proxyClient.getRuntimeMXBean();
          addRow(FIELD_GET + "RuntimeMXBean", "" + rmBean.getClass() + " " + passKwd);
          { indent++;
            String name = rmBean.getName();
            addRow("Name", validateStr(name, name != null && name.length() > 3 && name.indexOf("@") > -1  && name.indexOf("PID=") > -1  && name.indexOf("SID=") > -1));
            // name includes PID, and SID see sun.management.VMManagementImpl.getVmId
            String PID = name.substring(name.indexOf("PID=")+4, name.indexOf(";"));
            String SID = name.substring(name.indexOf("SID=")+4, name.indexOf(")"));
            // todo maybe check PID, SID are positive integers
            addRow("PID", validateInt(PID, 1, 0xffff));
            addRow("SID", validateInt(SID, 1, 0xffff));
            List<String> arglist = rmBean.getInputArguments();
            if (arglist != null && arglist.size() > 0)
              addRow("MODE", arglist.get(0));
            indent--;
          }
        } catch (Throwable  thr) { 
          addRow(FIELD_GET + "RuntimeMXBean", "" + rmBean.getClass() + " " + failKwd);
          out(thr);
        }

        OperatingSystemMXBean osMBean = null;
        try {
          osMBean = proxyClient.getOperatingSystemMXBean();
          addRow(FIELD_GET + "OperatingSystemMXBean", "" + osMBean.getClass() + " " + passKwd);
        } catch (Throwable  thr) { 
          addRow(FIELD_GET + "OperatingSystemMXBean", "" + osMBean.getClass() + " " + failKwd);
          out(thr);
        }

        // This code is currently disabled [bug 10145316]
        // because not all client java envs provide com.sun.management.*
        //
        // com.sun.management.OperatingSystemMXBean sunOSMBean  = null;
        // OperatingSystemMXBean platfOSMBean  = null;
        // 
        // try {
        //   platfOSMBean = proxyClient.getSunOperatingSystemMXBean();
        //   addRow(FIELD_GET + "com.sun.management.OperatingSystemMXBean", 
        //          "" + platfOSMBean.getClass() + " " + passKwd);
        // } catch (Throwable  thr) { 
        //   addRow(FIELD_GET + "com.sun.management.OperatingSystemMXBean", 
        //          "" + platfOSMBean.getClass() + " " + failKwd);
        //   out(thr);
        // }
        // 
        // long uptime = rmBean.getUptime();
        // 
        // { indent++;
        //   if (platfOSMBean != null) {
        //     long uptimeNano = platfOSMBean.getProcessCpuTime();
        //     if (uptimeNano == 0) 
        //       addRow("Uptime, ms",
        //              validateLong(rmBean.getUptime(), 100, 3600000),
        //              "Process CPU time",
        //              validateLong(uptimeNano, 0, 0));
        //     else 
        //       addRow("Uptime, ms",
        //              validateLong(rmBean.getUptime(), 100, 3600000),
        //              "Process CPU time",
        //              validateLong(uptimeNano, uptime * 1000000,  (10+ uptime) * 1000000));
        //   } else {
        //     addRow("Uptime", validateLong(uptime, 100, HOUR*MINUTE*SECOND));
        //   }
        // 
        //   if (platfOSMBean != null) {
        //     addRow(Resources.getText("Total physical memory"),
        //            formatKB(platfOSMBean.getTotalPhysicalMemorySize(), 0, 0),
        //            Resources.getText("Free physical memory"),
        //            formatKB(platfOSMBean.getFreePhysicalMemorySize(), 0, 0));
        //     addRow(Resources.getText("Committed virtual memory"),
        //            formatKB(platfOSMBean.getCommittedVirtualMemorySize(), 0, 0));
        //   }
        //   indent--; 
        // }

        CompilationMXBean  cmpMBean = null;
        try {
          cmpMBean = proxyClient.getCompilationMXBean();
          addRow(FIELD_GET + "CompilationMXBean", "" + cmpMBean.getClass() + " " + passKwd);
        } catch (Throwable  thr) { 
          addRow(FIELD_GET + "CompilationMXBean", "" + cmpMBean.getClass() + " " + failKwd);
          out(thr);
        }

        if (cmpMBean.isCompilationTimeMonitoringSupported()) {
          addRow("Total compile time",
                 validateLong(cmpMBean.getTotalCompilationTime(), 0, HOUR*MINUTE*SECOND));
        }

        // threads
        // addRow("Threads");
      
        { // indent++;

          ThreadMXBean tmBean = null;
          try {
            tmBean = proxyClient.getThreadMXBean();
            addRow(FIELD_GET + "ThreadMXBean", "" + tmBean.getClass() + " " + passKwd);
          } catch (Throwable  thr) { 
            if (DEBUG)
              out(thr);
            addRow(FIELD_GET + "ThreadMXBean", "" + tmBean.getClass() + " " + failKwd);
          }

          { indent++; 
            int tlCount = tmBean.getThreadCount();
            int tdCount = tmBean.getDaemonThreadCount();
            int tpCount = tmBean.getPeakThreadCount();
            long ttCount = tmBean.getTotalStartedThreadCount();
            addRow(Resources.getText("Live Threads"),   validateLong(tlCount, 5, 1000),
                   Resources.getText("Peak"),           validateLong(tpCount, 5, 1000));
            addRow(Resources.getText("Daemon threads"), validateLong(tdCount, 2, 100));
            addRow(Resources.getText("Total started"),  validateLong(ttCount, 6, 2000));

            if (USE_getAllThreadIds) {
              indent++;
              ThreadInfo[]  ttia = tmBean.getThreadInfo(tmBean.getAllThreadIds());
              for (int i = 0; i < ttia.length; i++) {
                ThreadInfo tti = ttia[i];
                String ttiString = tti.toString();

                StackTraceElement[] ttiStack = tti.getStackTrace();
                addRow("ThreadInfo", validateStr(ttiString, ttiString.length() > 30),
                       "stack size", validateLong(ttiStack.length, 2, 50));
                for (StackTraceElement e : tti.getStackTrace()) {
                  addRow(" . . . ", e.toString());
                }

              }
              indent--;
            }
            else
              {
                indent++;
            
                // addRow("another method");

                for (long threadID : tmBean.getAllThreadIds()) {
                  ThreadMXBean threadMBean = proxyClient.getThreadMXBean();
                  ThreadInfo ti = threadMBean.getThreadInfo(threadID, Integer.MAX_VALUE);
                  if (ti != null) {
                    String ttiString = ti.toString();
                    StackTraceElement[] ttiStack = ti.getStackTrace();
                    addRow("ThreadInfo", validateStr(ttiString, ttiString.length() > 30));

                    { indent++;
                      if (ti.getLockName() == null) {
                        addRow("Name",
                               ti.getThreadName(),
                               "State",
                               ti.getThreadState().toString());
                      } else if (ti.getLockOwnerName() == null) {
                        addRow("Name",
                               ti.getThreadName(),
                               "State",
                               ti.getThreadState().toString(),
                               "LockName",
                               ti.getLockName());
                      } else {
                        addRow("Name",
                               ti.getThreadName(),
                               "State", 
                               ti.getThreadState().toString(),
                               "LockName",
                               ti.getLockName(),
                               "LockOwner",
                               ti.getLockOwnerName());
                      }
                      addRow("BlockedCount",
                             ""+ti.getBlockedCount(),
                             "WaitedCount",
                             ""+ti.getWaitedCount());

                      StackTraceElement[] elts = ti.getStackTrace();
                      addRow("Stack trace size", validateLong(elts.length, 1, 50));
                      indent++;
                      int frameIdx = 1;
                      for (StackTraceElement e : elts) {
                        String elt = "" + e;
                        addRow("" + (frameIdx++), validateStr(elt, 
                                                              elt.lastIndexOf(".") > 2 && elt.indexOf("(") > 5));
                      }
                      indent--; }
                    indent--; 
                  }
                }
                indent--; 
              }
            indent--; 
          }
          // indent--; 
        }

        MemoryMXBean memoryBean = null;

        {
          try {
            memoryBean = proxyClient.getMemoryMXBean();
            addRow(FIELD_GET + "MemoryMXBean", "" + memoryBean.getClass() + " " + passKwd);
          } catch (Throwable  thr) { 
            out(thr);
            addRow(FIELD_GET + "MemoryMXBean", "" + memoryBean.getClass() + " " + failKwd);
          }

          { indent++;
            MemoryUsage u = memoryBean.getHeapMemoryUsage();
            long finalizerQueueLength = memoryBean.getObjectPendingFinalizationCount();
            addRow(Resources.getText("Current heap size"), 
                   formatKB(u.getUsed(), 32,u.getCommitted()));
            addRow(Resources.getText("Committed memory"),  
                   formatKB(u.getCommitted(), 64, u.getMax()));
            addRow(Resources.getText("Maximum heap size"), 
                   formatKB(u.getMax(), -1, 8*MEM_POOL_MAX_SZ_KB));
            addRow(Resources.getText("Objects pending for finalization"), 
                   validateLong(finalizerQueueLength, 0, 10000));
          

            boolean verbose_val = memoryBean.isVerbose();
            memoryBean.setVerbose(!verbose_val);
            boolean verbose_val_inverse = memoryBean.isVerbose();
            memoryBean.setVerbose(verbose_val);
            addRow("flip verbose flag", 
                   validateStr("", 
                               (memoryBean.isVerbose() == verbose_val &&
                                verbose_val != verbose_val_inverse)));
          
            indent--; 
          }
      
          // addRow("Collectors");

          Collection<GarbageCollectorMXBean> garbageCollectors =
            proxyClient.getGarbageCollectorMXBeans();

          long totalGcCount = 0;
        
          for (GarbageCollectorMXBean garbageCollectorMBean : garbageCollectors) {
            String gcName = garbageCollectorMBean.getName();
            long gcCount = garbageCollectorMBean.getCollectionCount();
            totalGcCount += gcCount;
            long gcTime = garbageCollectorMBean.getCollectionTime();

            addRow("GarbageCollectorMXBean", ""+garbageCollectorMBean.getClass());
            addRow(Resources.getText("GcInfo", gcName, gcCount,
                                     (gcTime < 0 
                                      ? Resources.getText("Unavailable")
                                      : formatTime(gcTime))));
          }

          addRow("Perform gc...");
          memoryBean.gc();
          MemoryUsage u_afterGC = memoryBean.getHeapMemoryUsage();
          long totalGcCount_afterGC = 0;
          for (GarbageCollectorMXBean garbageCollectorMBean : garbageCollectors) {
            String gcName = garbageCollectorMBean.getName();
            long gcCount = garbageCollectorMBean.getCollectionCount();
            totalGcCount_afterGC += gcCount;
          }
          addRow("Total collection count", 
                 // it seems 300 extra collections since the button pressed is enough...
                 // it is typically 15..35 depending on client/server connection speed
                 // and the intensity of GC workload
                 validateLong(totalGcCount_afterGC, totalGcCount, totalGcCount+300));
        }

        addRow("Memory Pools");
        Collection<MemoryPoolProxy> poolProxies = proxyClient.getMemoryPoolProxies();      
        if (poolProxies != null) 
          addRow(FIELD_GET + "Pool Proxy Collection", "" + passKwd);
        else 
          addRow(FIELD_GET + "Pool Proxy Collection", "" + failKwd);

          
        { indent++;

          addRow("Number of Proxies", validateLong(poolProxies.size(), 6, 10));

          for (MemoryPoolProxy poolProxy : poolProxies) {
            try {
              MemoryPoolStat stat = poolProxy.getStat();
              { indent++;
                String name = stat.getPoolName();
                addRow("Name", validateStr(name, name != null && name.length() > 3));
                MemoryUsage u = stat.getUsage();
                addRow("Current size", 
                       formatKB(u.getUsed(), 0, Math.max(0, u.getMax())),
                       Resources.getText("Committed memory"),  
                       formatKB(u.getCommitted(), 0, Math.max(0, u.getMax())));
            
                addRow(Resources.getText("Maximum heap size"), 
                       formatKB(u.getMax(), -1, MEM_POOL_MAX_SZ_KB));
                indent--; 
              } 
            } catch (Exception e) {
              addRow("Got Exception while accessing Mem Pool Proxy " + poolProxy);
              out(e);
            }
          }
          indent--; }

        // addRow("Classes");
      
        ClassLoadingMXBean clMBean = null;
        try {
          clMBean = proxyClient.getClassLoadingMXBean();
          addRow(FIELD_GET + "ClassLoadingMXBean", "" + clMBean.getClass() + " " + passKwd);
        } catch (Throwable  thr) { 
          out(thr);
          addRow(FIELD_GET + "ClassLoadingMXBean", "" + clMBean.getClass() + " " + failKwd);
        }

        if (clMBean != null) { indent++;
        
          long clCount = clMBean.getLoadedClassCount();
          long cuCount = clMBean.getUnloadedClassCount();
          long ctCount = clMBean.getTotalLoadedClassCount();
          addRow(Resources.getText("Current classes loaded"), validateLong(clCount, 1000, 3000),
                 Resources.getText("Total classes unloaded"), validateLong(cuCount, 0,0));
          addRow(Resources.getText("Total classes loaded"),
                 validateLong(ctCount, clCount, clCount));

          indent--; 


          {// OracleRuntimeMXBean.  
            MBeanInfo info = null;
            String beanName  = "oracle.jvm:type=OracleRuntime";
            ObjectName name  = toObjectName(beanName);
            Object val = null;
            MBeanServerConnection server = proxyClient.getMBeanServerConnection();
            try {
              info = server.getMBeanInfo(name);
              addRow(FIELD_GET + "OracleRuntimeMXBean", "" + info + " " + passKwd);
            } catch (Throwable  thr) { 
              out(thr);
              addRow(FIELD_GET + "OracleRuntimeMXBean", "" + info + " " + failKwd);
            }

            // get a few attributes
            indent++;
            {
              String[]  attributeNames = {
                "Platform", "WholeJVM_ExecutionElapsedTime",
                "WholeJVM_CallHeapCollectedCount"
              };
              for (int i = 0; i < attributeNames.length; i++) {
                try {
                  val = server.getAttribute(name, attributeNames[i]);
                  addRow(FIELD_GET + "OracleRuntimeMXBean", attributeNames[i] + " = " + val + " " + passKwd);
                } catch (Throwable thr) {
                  out(thr);
                  addRow(FIELD_GET + "OracleRuntimeMXBean", attributeNames[i] + " attribute " + failKwd);
                }
              }
            }
            indent--;

          }


          if (measureAverageRoundtripTime) {

            long rt_start = System.currentTimeMillis();
            for (int rti = 0; rti < 1000; rti++)
              clCount = clMBean.getLoadedClassCount();          
            addRow("Roundtrip time, average, ms: " + 
                   (System.currentTimeMillis() - ((double)rt_start)));

            rt_start = System.currentTimeMillis();

            // [bug 10145316]
            // if (platfOSMBean != null) platfOSMBean.getProcessCpuTime(); 

            if (clMBean != null ) clMBean.getLoadedClassCount(); 
            if (memoryBean != null) memoryBean.isVerbose();

            
            // [bug 10145316]
            // if (platfOSMBean != null) platfOSMBean.getProcessCpuTime(); 

            if (clMBean != null ) clMBean.getLoadedClassCount(); 
            if (memoryBean != null) memoryBean.isVerbose();
            addRow("Roundtrip time, single, diff beans ms: " + 
                   (((System.currentTimeMillis() - ((double)rt_start)))/6));
          }

        }

      }

      if (beanInfoList.size() > 0) {
        if (verbose) outln(" *** Individual MBean Info *** ");
        Iterator it = beanInfoList.iterator();
        while (it.hasNext()) {
          String beanName = (String)it.next();
          ObjectName name  = toObjectName(beanName);

          if (name != null) { // get bean info
            MBeanInfo info = null;
            try {
              MBeanServerConnection server = proxyClient.getMBeanServerConnection();
              info = server.getMBeanInfo(name);
              addRow(FIELD_GET + " Bean info " + beanName, "" + passKwd);

              if (info != null) {
                indent++;
                MBeanAttributeInfo[] attrs = info.getAttributes();
                addRow("Attributes");
                indent++;
                for (MBeanAttributeInfo attr : attrs) {
                  if (attr != null) {
                    showAttributeInfo(attr);
                    indent++;
                    try {
                      Object val = server.getAttribute(name, attr.getName());
                      addRow("Value", ""+val);
                    } catch (Exception e) {
                      if (DEBUG) out(e);
                      addRow("Value", "unavailable");
                    }
                    indent--;
                  }
                }
                indent--;

                MBeanOperationInfo[] ops = info.getOperations();
                addRow("Operations");
                indent++;
                for (MBeanOperationInfo op : ops) {
                  if (op != null) {
                    showOperationInfo(op);
                  }
                }
                indent--;


                indent--;
              }
            } catch (InstanceNotFoundException e) {
              addRow(FIELD_GET + " Bean does not exit", beanName + " " + failKwd);
              out(e);
            } catch (ReflectionException e1) {
              addRow(FIELD_GET + " Bean inacessible", beanName + " " + failKwd);
              out(e1);
            }
          }
        }
      }

      if (beanGettersList.size() > 0) {
        if (verbose) outln(" *** Get Specific MBean Attributes *** ");
        Iterator it = beanGettersList.iterator();
        while (it.hasNext()) {
          String beanName  = (String) it.next();
          ObjectName name  = toObjectName(beanName);
          String attrName  = (String) it.next();

          if (name != null) { // get bean info
            Object val = null;
            try {
              MBeanServerConnection server = proxyClient.getMBeanServerConnection();
              val = server.getAttribute(name, attrName);
              if (val != null && val.getClass().isArray()) {
                Object[] arr = (Object[])val;
                String val_print  = "{";
                for (int i = 0; i < arr.length; i++) {
                  val_print += " " + arr[i];
                }
                val_print  += " }";
                val = val_print;
              }
              addRow(FIELD_GET + " Bean attribute", attrName + " " + passKwd);
            } catch (InstanceNotFoundException e) {
              addRow(FIELD_GET + " Bean does not exist", beanName + " " + failKwd);
              out(e);
            } catch (AttributeNotFoundException e) {
              addRow(FIELD_GET + " Bean attribute does not exist", attrName + " " + failKwd);
              out(e);
            } catch (ReflectionException e) {
              addRow(FIELD_GET + " Problem retrieving attribute ",  attrName + " " + failKwd);
              out(e);
            }

            addRow(FIELD_GET + " Attribute '" + attrName + "' value", "" + val);
          }
        }
      }

      if (beanSettersList.size() > 0) {
        if (verbose) outln(" *** Set Specific MBean Attributes *** ");
        Iterator it = beanSettersList.iterator();
        while (it.hasNext()) {
          String beanName  = (String) it.next();
          String attrName  = (String) it.next();
          String literal  = (String) it.next();
          ObjectName name  = toObjectName(beanName);
          Object val = literal;

          if (name != null) { // set attribute
            try {
              MBeanServerConnection server = proxyClient.getMBeanServerConnection();
              // first, get the attribute to determine the type of it
              val = server.getAttribute(name, attrName);
              if (val != null) val = coerceAttributeLiteralTo(literal, val);
              // second, set the new value
              Attribute attr = new Attribute(attrName, val);
              server.setAttribute(name, attr);
              // last, retrieve the new value and print out
              val = server.getAttribute(name, attrName);
              addRow(FIELD_SET + beanName + "." + attrName,  ""+ val + " " + passKwd);
            } catch (InstanceNotFoundException e) {
              addRow(FIELD_SET + beanName + "." + attrName,  ""+ val + " " + failKwd);
              out(e);
            } catch (AttributeNotFoundException e) {
              addRow(FIELD_SET + beanName + "." + attrName, ""+ val + " " + failKwd);
              out(e);
            } catch (ReflectionException e) {
              addRow(FIELD_SET + beanName + "." + attrName, ""+ val + " " + failKwd);
              out(e);
            }
          }
        }
      }

      if (beanOperationsList.size() > 0) {
        if (verbose) outln(" *** Invoke Specific MBean Operations *** ");
        Iterator it = beanOperationsList.iterator();
        while (it.hasNext()) {
          String beanName  = (String) it.next();
          String opName  = (String) it.next();
          String[] paramSign = (String[]) it.next();
          Object[] params = (Object[]) it.next();

          ObjectName name  = toObjectName(beanName);

          if (name != null) { // get bean info
            Object val = null;
            try {
              MBeanServerConnection server = proxyClient.getMBeanServerConnection();
              val = server.invoke(name, opName, params, paramSign);
              addRow(FIELD_GET + " Bean operation " + beanName + ":" + opName, "" + passKwd);
            } catch (InstanceNotFoundException e) {
              addRow(FIELD_GET + " Bean does not exist", beanName + " " + failKwd);
              out(e);
            } catch (ReflectionException e) {
              addRow(FIELD_GET + " Bean operation " + beanName + "." + opName, "" + failKwd);
              out(e);
            }
            String params_str = "";
            for (int pi = 0; pi < params.length; pi++)
              params_str += (pi == 0 ? "" : ",") + params[pi];
            addRow("Result of " + beanName + ":" + opName + "(" + params_str + ") :", ""+val);
          }
        }
      }

      
    } catch (IOException e) {
      addRow(failKwd + "Got IO exception " + e);
      out(e);
    } catch (Exception e) {
      addRow(failKwd + " Got exception " + e);
      out(e);
    }

  }
}